
    (<iA                    ,   d Z ddlmZ ddlZddlZddlmZmZ ddlm	Z	m
Z
mZ ddlZddlmZ  ej                  d      dd	       Z ej                         dd
       Z G d d      ZddZ G d d      Z G d d      Z G d d      Z G d d      Zy)uk   
test_server.py - Whisper GPU HTTP 서비스 단위 테스트 (GPU 모델 로딩 없이 API 구조 검증)
    )annotationsN)Any	Generator)	AsyncMock	MagicMockpatch)
TestClientT)autousec               #     K   t               } t        dddd      gt        dd      f| j                  _        t        d| 	      5  d
dl}| |_        d|_        |  ddd       y# 1 sw Y   yxY ww)uP   WhisperModel 생성자를 Mock으로 교체하여 GPU 로딩을 건너뜁니다.              @u    안녕하세요.N)startendtextwordsko)languagedurationzfaster_whisper.WhisperModelreturn_valuer   )r   
transcriber   r   servermedium_modelsmall_model)
mock_modelr   s     Q/home/jay/workspace/.worktrees/task-2057-dev2/services/whisper-gpu/test_server.pymock_whisper_modelr      s      J (		
 	4#.
*J& 
,:	F (!  s   AA2A&	A2&A/+A2c                8    ddl }t        |j                  d      S )u&   FastAPI TestClient를 반환합니다.r   NF)raise_server_exceptions)r   r	   app)r   r   s     r   clientr!   +   s     fjj%@@    c                  T    e Zd ZddZddZddZddZddZddZddZ	ddZ
dd	Zy
)TestHealthEndpointc                H    |j                  d      }|j                  dk(  sJ y )N
/v1/health   )getstatus_codeselfr!   responses      r   test_health_returns_200z*TestHealthEndpoint.test_health_returns_2008   s$    ::l+##s***r"   c                    |j                  d      }|j                         }d|v sJ d|v sJ d|v sJ d|v sJ d|v sJ d|v sJ y )Nr&   statusmodelsdevicegpu_memory_used_mb	last_usedunload_timeout_secr(   jsonr+   r!   r,   datas       r   test_health_response_schemaz.TestHealthEndpoint.test_health_response_schema<   so    ::l+}}444#t+++d"""#t+++r"   c                Z    |j                  d      }|j                         }|d   dk(  sJ y )Nr&   r/   okr5   r7   s       r   test_health_status_okz(TestHealthEndpoint.test_health_status_okF   s-    ::l+}}H~%%%r"   c                Z    |j                  d      }|j                         }|d   dk(  sJ y )Nr&   r1   cudar5   r7   s       r   test_health_device_cudaz*TestHealthEndpoint.test_health_device_cudaK   s-    ::l+}}H~'''r"   c                    |j                  d      }|j                         }|d   }t        |t              sJ d|v sJ d|v sJ |d   dv sJ |d   dv sJ y )Nr&   r0   mediumsmall)loadedunloaded)r(   r6   
isinstancedict)r+   r!   r,   r8   r0   s        r   "test_health_models_field_structurez5TestHealthEndpoint.test_health_models_field_structureP   sw    ::l+}}h&$'''6!!!&   h#9999g"8888r"   c                `    |j                  d      }|j                         }|d   d   dk(  sJ y)u<   medium_model이 설정된 상태에서 loaded 반환 확인.r&   r0   rA   rC   Nr5   r7   s       r   test_health_shows_loaded_modelz1TestHealthEndpoint.test_health_shows_loaded_modelZ   s2    ::l+}}H~h'8333r"   c                `    |j                  d      }|j                         }|d   d   dk(  sJ y)u1   small_model이 None이면 unloaded 반환 확인.r&   r0   rB   rD   Nr5   r7   s       r    test_health_shows_unloaded_modelz3TestHealthEndpoint.test_health_shows_unloaded_modela   s2    ::l+}}H~g&*444r"   c                Z    |j                  d      }|j                         }|d   dk(  sJ y)u(   unload_timeout_sec이 600 반환 확인.r&   r4   iX  Nr5   r7   s       r    test_health_unload_timeout_valuez3TestHealthEndpoint.test_health_unload_timeout_valueh   s/    ::l+}}()S000r"   c                p    |j                  d      }|j                         }t        |d   t              sJ y )Nr&   r2   )r(   r6   rE   intr7   s       r   test_health_gpu_memory_is_intz0TestHealthEndpoint.test_health_gpu_memory_is_intn   s1    ::l+}}$34c:::r"   Nr!   r	   returnNone)__name__
__module____qualname__r-   r9   r<   r?   rG   rI   rK   rM   rP    r"   r   r$   r$   7   s/    +,&
(
9451;r"   r$   c                      y)uP   최소한의 유효한 WebM/Ogg 더미 바이트 (실제 전사는 Mock 처리).sD   Eߣ                                                                rW   rW   r"   r   _make_dummy_audiorY   x   s    *r"   c                  x    e Zd ZddZ	 	 	 	 	 	 ddZ	 	 	 	 ddZddZ	 	 	 	 ddZ	 	 	 	 ddZddZ	ddZ
dd	Zy
)TestTranscribeEndpointc                H    |j                  d      }|j                  dk(  sJ y)u&   파일도 URL도 없으면 422 반환./v1/transcribei  N)postr)   r*   s      r   )test_transcribe_missing_input_returns_422z@TestTranscribeEndpoint.test_transcribe_missing_input_returns_422~   s%    ;;/0##s***r"   c           
     L   t        dd      5  t        dt              5 }ddddd	gd
dd|_        t               }|j	                  dddt        j                  |      dfid
ddd      }ddd       ddd       j                  dk(  sJ y# 1 sw Y   #xY w# 1 sw Y   'xY w)u8   오디오 파일 업로드 시 200 반환 (Mock 전사).server.get_audio_duration      $@r   server.run_transcriptionnew_callable   안녕하세요.r   r   r   r   r   r   r   segmentsr   r   r]   file	test.webm
audio/webmrA   r6   )r   modelformatfilesr8   Nr'   )r   r   r   rY   r^   ioBytesIOr)   )r+   r!   r   
mock_transaudio_bytesr,   s         r   %test_transcribe_with_file_returns_200z<TestTranscribeEndpoint.test_transcribe_with_file_returns_200   s    
 -DA	,9E	IS +'*3@RST 	'J# ,-K{{ RZZ-DlST"&VL # H	 	  ##s***!	 	 	 	s#   BAB,BB	BB#c           
        t        dd      5  t        dt              5 }ddddd	gd
dd|_        t               }|j	                  dddt        j                  |      dfi      }ddd       ddd       j                  dk(  sJ |j                         }d|v sJ d|v sJ d|v sJ d|v sJ y# 1 sw Y   KxY w# 1 sw Y   OxY w)uC   응답 스키마 검증: text, segments, language, duration 필드.ra   rb   r   rc   rd   	   테스트r         ?rg   r   rh   r]   rj   rk   rl   rp   Nr'   r   ri   r   r   	r   r   r   rY   r^   rq   rr   r)   r6   r+   r!   rs   rt   r,   r8   s         r   test_transcribe_response_schemaz6TestTranscribeEndpoint.test_transcribe_response_schema   s    
 -DA	,9E	IS $'*3LM 	'J# ,-K{{ RZZ-DlST # H	 	 ##s***}}~~T!!!T!!!T!!!)	 	 	 	s#   B=AB1'B=1B:	6B==Cc           
        t        dd      5  t        dt              5 }ddddd	gd
dd|_        t               }|j	                  dddt        j                  |      dfi      }ddd       ddd       j                         }t        |d   t              sJ |d   r|d   d   }d|v sJ d|v sJ d|v sJ yy# 1 sw Y   WxY w# 1 sw Y   [xY w)u6   segments 각 항목에 start, end, text 필드 확인.ra   rb   r   rc   rd   rw   r   rx   rg   r   rh   r]   rj   rk   rl   ry   Nri   r   r   r   r   )
r   r   r   rY   r^   rq   rr   r6   rE   list)r+   r!   rs   rt   r,   r8   segs          r   test_transcribe_segments_schemaz6TestTranscribeEndpoint.test_transcribe_segments_schema   s    -DA	,9E	IS $'*3LM 	'J# ,-K{{ RZZ-DlST # H	 	 }}$z*D111
z"1%Cc>!>C<<S= =	 #	 	 	 	s#   C	AB='C	=C	C		Cc           	     T   t        dt              5 }t        dd      5  t        dt              5 }d|_        dg d	dd
|_        |j                  ddd	d      }ddd       ddd       ddd       j                  dk(  sJ y# 1 sw Y   +xY w# 1 sw Y   /xY w# 1 sw Y   3xY w)u%   audio_url JSON 입력 시 200 반환.zserver.download_audio_from_urlrd   ra   g      @r   rc   z/tmp/fake_audio.wavu   URL 전사 테스트r   rh   r]   zhttp://example.com/test.wav)	audio_urlr   )r6   Nr'   )r   r   r   r^   r)   )r+   r!   mock_dlrs   r,   s        r   *test_transcribe_with_audio_url_returns_200zATestTranscribeEndpoint.test_transcribe_with_audio_url_returns_200   s    
 2K	OV-C@	 ,9E	 JT#8G . 	'J# {{ #@dS # H	 	 	  ##s***!	 	 	 	 	 	s9   BB*BB$BBBB	BB'c           
     l   t        dd      5  t        dt              5 }dg ddd	|_        t               }|j	                  d
ddt        j                  |      dfi      }ddd       ddd       j                  dk(  sJ |j                         }|d   dk(  sJ y# 1 sw Y   =xY w# 1 sw Y   AxY w)u*   language 파라미터 기본값 ko 확인.ra   rb   r   rc   rd   u   기본 언어r   rx   rh   r]   rj   rk   rl   ry   Nr'   r   rz   r{   s         r   &test_transcribe_default_language_is_koz=TestTranscribeEndpoint.test_transcribe_default_language_is_ko   s    
 -DA	,9E	IS ( 	'J# ,-K{{ RZZ-DlST # H	 	  ##s***}}J4'''%	 	 	 	s#   B*AB"B*B'	#B**B3c           
     ~   t        dd      5  t        dt              5 }dg ddd	|_        t               }|j	                  d
ddt        j                  |      dfiddi      }ddd       ddd       j                  dk(  sJ d|j                  j                  dd      v sJ y# 1 sw Y   CxY w# 1 sw Y   GxY w)u"   format=text 시 plain text 반환.ra   rb   r   rc   rd   u   텍스트 포맷r   rx   rh   r]   rj   rk   rl   rn   r   ro   Nr'   
text/plaincontent-type 
r   r   r   rY   r^   rq   rr   r)   headersr(   r+   r!   rs   rt   r,   s        r   test_transcribe_format_textz2TestTranscribeEndpoint.test_transcribe_format_text   s     -DA	,9E	IS + 	'J# ,-K{{ RZZ-DlST' # H	 	  ##s***x//33NBGGGG%	 	 	 	s#   B3AB'%B3'B0	,B33B<c           
        t        dd      5  t        dt              5 }ddddd	gd
dd|_        t               }|j	                  dddt        j                  |      dfiddi      }ddd       ddd       j                  dk(  sJ d|j                  j                  dd      v sJ y# 1 sw Y   CxY w# 1 sw Y   GxY w)u!   format=srt 시 SRT 형식 반환.ra   rb   r   rc   rd   u
   SRT 포맷r   rx   rg   r   rh   r]   rj   rk   rl   rn   srtro   Nr'   r   r   r   r   r   s        r   test_transcribe_format_srtz1TestTranscribeEndpoint.test_transcribe_format_srt       -DA	,9E	IS %'*3MN 	'J# ,-K{{ RZZ-DlST& # H	 	  ##s***x//33NBGGGG#	 	 	 	#   B8A
B,*B8,B5	1B88Cc           
        t        dd      5  t        dt              5 }ddddd	gd
dd|_        t               }|j	                  dddt        j                  |      dfiddi      }ddd       ddd       j                  dk(  sJ d|j                  j                  dd      v sJ y# 1 sw Y   CxY w# 1 sw Y   GxY w)u!   format=vtt 시 VTT 형식 반환.ra   rb   r   rc   rd   u
   VTT 포맷r   rx   rg   r   rh   r]   rj   rk   rl   rn   vttro   Nr'   r   r   r   r   r   s        r   test_transcribe_format_vttz1TestTranscribeEndpoint.test_transcribe_format_vtt%  r   r   NrQ   )r!   r	   r   r   rR   rS   )rT   rU   rV   r_   ru   r|   r   r   r   r   r   r   rW   r"   r   r[   r[   }   sz    +
+ +6?+	+," "	"4!2+ +	+,( (	(0H,H*Hr"   r[   c                  4    e Zd ZddZddZddZddZddZy)TestModelSelectionc                <    ddl }|j                  d      }|dk(  sJ y)u%   30분 미만 → small 모델 선택.r   Ng     @rB   r   select_model_for_durationr+   r   selecteds      r   'test_select_small_model_for_short_audioz:TestModelSelection.test_select_small_model_for_short_audio@  s#    33I>7"""r"   c                <    ddl }|j                  d      }|dk(  sJ y)u5   30분 이상 2시간 미만 → medium 모델 선택.r   Ng      @rA   r   r   s      r   )test_select_medium_model_for_medium_audioz<TestModelSelection.test_select_medium_model_for_medium_audioG  #    33I>8###r"   c                <    ddl }|j                  d      }|dk(  sJ y)u(   2시간 이상 → medium 모델 선택.r   Ng     @rA   r   r   s      r   'test_select_medium_model_for_long_audioz:TestModelSelection.test_select_medium_model_for_long_audioN  s#    33J?8###r"   c                <    ddl }|j                  d      }|dk(  sJ y)u-   경계값: 정확히 30분 → medium 모델.r   Ng      @rA   r   r   s      r   test_boundary_30minz&TestModelSelection.test_boundary_30minU  r   r"   c                <    ddl }|j                  d      }|dk(  sJ y)u(   경계값: 29분 59초 → small 모델.r   Ng     @rB   r   r   s      r   test_boundary_just_under_30minz1TestModelSelection.test_boundary_just_under_30min\  s#    33NC7"""r"   NrR   rS   )rT   rU   rV   r   r   r   r   r   rW   r"   r   r   r   ?  s    #$$$#r"   r   c                  ,    e Zd ZddZddZddZddZy)TestFormatConversionc                v    ddl }ddddddddg}|j                  |      }d	|v sJ d
|v sJ d|v sJ d|v sJ y)u   SRT 형식 변환 검증.r   Nr   r   rf   rg   g      @u   반갑습니다.z1
z2
z00:00:00,000)r   segments_to_srt)r+   r   ri   r   s       r   test_to_srt_formatz'TestFormatConversion.test_to_srt_formati  sj     #/AB#/AB
 $$X.||||$$$!S(((r"   c                z    ddl }ddddg}|j                  |      }|j                  d      sJ d|v sJ d|v sJ y)	u   VTT 형식 변환 검증.r   Nr   r   rf   rg   WEBVTTz00:00:00.000)r   segments_to_vtt
startswith)r+   r   ri   r   s       r   test_to_vtt_formatz'TestFormatConversion.test_to_vtt_formatw  sY     #/AB
 $$X.~~h'''$$$!S(((r"   c                <    ddl }|j                  d      }|dk(  sJ y)u)   SRT 타임스탬프 포맷: HH:MM:SS,mmm.r   N     @z01:01:01,500)r   seconds_to_srt_timestampr+   r   tss      r   test_srt_timestamp_formatz.TestFormatConversion.test_srt_timestamp_format  #    ,,V4^###r"   c                <    ddl }|j                  d      }|dk(  sJ y)u)   VTT 타임스탬프 포맷: HH:MM:SS.mmm.r   Nr   z01:01:01.500)r   seconds_to_vtt_timestampr   s      r   test_vtt_timestamp_formatz.TestFormatConversion.test_vtt_timestamp_format  r   r"   Nr   )rT   rU   rV   r   r   r   r   rW   r"   r   r   r   h  s    )
)$$r"   r   c                  $    e Zd ZddZddZddZy)TestLazyLoadc                    ddl }||_        ||_        t        dd      5 }t	               |j
                  _        |j                          ddd       |j                  J |j                  J y# 1 sw Y   &xY w)u>   _unload_models 호출 후 medium_model과 small_model이 None.r   Nzserver.torchT)create)r   r   r   r   r   r>   empty_cache_unload_models)r+   r   r   
mock_torchs       r   !test_unload_models_clears_globalsz.TestLazyLoad.test_unload_models_clears_globals  sv    0/>$/ 	$:*3+JOO'!!#	$ ""***!!)))		$ 	$s   *A//A8c                `    ddl }d|_        |j                  d      }|J |j                  J y)u8   medium_model이 None이면 get_model_instance가 로딩.r   NrA   )r   r   get_model_instance)r+   r   r   rm   s       r   )test_get_model_instance_lazy_loads_mediumz6TestLazyLoad.test_get_model_instance_lazy_loads_medium  s;    "))(3   ""...r"   c                    ddl }ddl}d|_        |j                         }|j                  d       |j                  |k\  sJ y)u3   get_model_instance 호출 후 _last_used가 갱신.r   Nr   rA   )r   time
_last_usedr   )r+   r   r   r   befores        r   )test_get_model_instance_updates_last_usedz6TestLazyLoad.test_get_model_instance_updates_last_used  s=    !!(+  F***r"   N)r   r   rR   rS   )rT   rU   rV   r   r   r   rW   r"   r   r   r     s    
*/+r"   r   )rR   z Generator[MagicMock, None, None])r   r   rR   r	   )rR   bytes)__doc__
__future__r   rq   r6   typingr   r   unittest.mockr   r   r   pytestfastapi.testclientr	   fixturer   r!   r$   rY   r[   r   r   r   rW   r"   r   <module>r      s    # 	  ! 5 5  )  , A A:; :;B+
{H {HD"# "#R'$ '$\+ +r"   