
    i7                        d dl Z d dlZd dlmZmZmZ d dlmZ d dl	Z
d dl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j,                  d
        Z ej,                  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 Z#d(dZ$d(dZ%d Z&d Z' G d d      Z(d  Z)	 d)d!Z*d" Z+d# Z,d$ Z-d% Z.d& Z/d' Z0y)*    N)	AsyncMock	MagicMockpatch)read)write	multipartz-python-multipart is required for server tests)reason)
TestClient)appc                       t        t              S N)r
   r        p/Volumes/fast/ai/experiments/voice-extract-mac/.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_modelr7   9   s^    {{=>H3&&&==?8    ""::<Hr   c                     t        d      |_        | j                  d      }|j                  dk(  sJ |j                  j	                  d       y )NTr   r0      r4   r   remove_modeldeleter"   r6   r$   s      r   test_remove_model_successr=   D   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_modeli  detailz$Model 'non_existent_model' not foundnon_existent_model)r   r;   r<   r"   r#   r6   r$   s      r   test_remove_model_not_foundrA   L   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"r9   test_model_quotesr:   r$   s      r   %test_remove_model_with_quotes_in_namerD   U   sH    '0d'C$}}HIH3&&&$$<<=PQr   c                       e Zd Zd Zy)MockAudioResultc                      || _         || _        y r   )audiosample_rate)self
audio_datarI   s      r   __init__zMockAudioResult.__init__^   s    
&r   N)__name__
__module____qualname__rL   r   r   r   rF   rF   ]   s    'r   rF   
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pirF   astypefloat32)rP   kwargsrI   duration	frequencytrK   s          r    sync_mock_audio_stream_generatorr`   c   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voicez/v1/audio/speech)r#   r   zcontent-type	audio/mp3zcontent-dispositionzattachment; filename=speech.mp3r   rf   rg   z(Failed to read or validate MP3 content: )r   r`   generater   r5   r"   headerslowerr6   assert_called_once	call_argsr!   
audio_readioBytesIOcontentr-   	Exceptionpytestfail)
r   r   mock_tts_modelpayloadr%   argsr\   rK   rI   es
             r   test_tts_speechry   l   s   [N'.NON%.N%K"(=7SG{{-G{<H3&&&N+113{BBB./557,	-	- ""::;KL..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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   rR   r,   rS   r   FrT   r*   mp3)formatz/v1/audio/transcriptionsfileztest.mp3rh   re   test_stt_model)filesr   r   z/tmp/)r   ri   r   rU   rV   rW   rX   rY   rZ   r[   ro   rp   audio_writeseekr5   r"   r#   r6   rl   rm   
startswith)
r   r   mock_stt_modelrI   r]   r^   r_   rK   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""::;KL..0"",,Q/2==gFFFr   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  )rU   rV   rW   rX   rY   rZ   int16)
duration_srI   r_   rH   s       r   make_speech_audior      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)rU   zerosrW   r   )r   rI   s     r   make_silence_audior      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streamlanguage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{   segmentsr   )iterr   join)rH   r   r   r   r\   deltass        r   ri   z*_make_streaming_generate.<locals>.generate   s3    <0004$
 	
r   r   )r   ri   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   )rH   r   r   r\   r{   s       r   ri   z._make_non_streaming_generate.<locals>.generate   s    dTDAAr   r   )r{   ri   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{   )rJ   r{   s     r   rL   zMockChunk.__init__   s	    	r   N)rM   rN   rO   __doc__rL   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)rw   r\   callsfns     r   wrapperz_trackable.<locals>.wrapper   s#    dF^$4"6""r   )	functoolsrb   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   z
test-modelrR   )re   rI   z!/v1/audio/transcriptions/realtimer2   readyg      @i  r   actionstopN)r   r   ri   r   updatewebsocket_connect	send_jsonreceive_jsonr   ranger-   
send_bytestobytesr   rr   )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.Hello 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_chunksr   1  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   ri   r-   r   
isinstancemxarrayr   )r   r   r   r   r   tracked	first_args          r   test_realtime_ws_mx_array_passr   C  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   )rZ   r   bfloat16r   r   )rH   r   r   r   r\   r   s         r   r   z@test_realtime_ws_mx_array_supports_bfloat16_cast.<locals>.gen_fnT  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   Q  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_fallbackr   a  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)rR   r   )1r   ro   unittest.mockr   r   r   mlx.corecorer   numpyrU   rs   mlx_audio.audio_ior   rn   r   r   importorskipfastapi.testclientr
   mlx_audio.serverr   fixturer   r   r&   r.   r7   r=   rA   rD   rF   r   r`   ry   r   r   r   r   r   r   r   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>GNC
 
 <@$B$03$X( Qr   