
    Ri{Q                     P   d Z ddlZddlZddlZddlmZ ddlmZm	Z	 ddl
Z
 ee      j                  j                  Zej                  j                  d ee             edz  Zej$                  j'                  de      ZeJ ej$                  j+                  e      Zej.                  J eej0                  d<   ej.                  j3                  e        G d d      Z G d	 d
      Z G d d      Z G d d      Z G d d      Z G d d      Z G d d      Z  G d d      Z!y)ue  
test_youtube_transcribe.py

scripts/youtube-transcribe.py 단위 테스트 (TDD)

테스트 항목:
1. argparse: 필수/선택 인자 파싱 정상 동작
2. download_audio: yt-dlp 호출 및 WAV 파일 반환
3. transcribe_audio: HTTP multipart/form-data 전송 정상 처리
4. transcribe_audio: 연결 실패 시 fallback 경고 반환
5. transcribe_audio: 타임아웃 시 fallback 경고 반환
6. format_output: text/json/srt 형식 변환
7. main: 정상 흐름 통합 (mock)
8. main: 로컬 서비스 불가 시 fallback 메시지
9. main: exit code 성공 0
10. main: exit code 실패 1 (yt-dlp 에러)
    N)Path)	MagicMockpatchzyoutube-transcribe.pyyoutube_transcribec                   *    e Zd Zd Zd Zd Zd Zd Zy)TestParseArgsc                     t        j                  t              5  t        j	                  g        ddd       y# 1 sw Y   yxY w)u)   --url 없이 실행 시 SystemExit 발생Npytestraises
SystemExitr   
parse_argsselfs    V/home/jay/workspace/.worktrees/task-2117-dev1/scripts/tests/test_youtube_transcribe.pytest_url_requiredzTestParseArgs.test_url_required0   s1    ]]:& 	.))"-	. 	. 	.s	   9Ac                     t         j                  ddg      }|j                  dk(  sJ |j                  dk(  sJ |j                  J |j
                  dk(  sJ y)u3   --url만 주면 기본값으로 나머지 채워짐--urlhttps://youtube.com/watch?v=abctextNkor   r   urlformatoutputlanguager   argss     r   test_url_onlyzTestParseArgs.test_url_only5   s_    !,,g7X-YZxx<<<<{{f$$${{"""}}$$$    c                     t         j                  g d      }|j                  dk(  sJ |j                  dk(  sJ |j                  dk(  sJ |j
                  dk(  sJ y)u,   모든 인자 지정 시 올바르게 파싱)r   r   --formatjson--output/tmp/out.json
--languageenr   r#   r%   r'   Nr   r   s     r   test_all_argszTestParseArgs.test_all_args=   sb    !,,	
 xx<<<<{{f$$${{o---}}$$$r    c                     t        j                  t              5  t        j	                  g d       ddd       y# 1 sw Y   yxY w)u!   format은 text|json|srt만 허용)r   r   r"   xmlNr
   r   s    r   test_format_choicesz!TestParseArgs.test_format_choicesP   s6    ]]:& 	k))*ij	k 	k 	ks	   ;Ac                 T    t         j                  g d      }|j                  dk(  sJ y)u   srt 포맷 파싱)r   r   r"   srtr-   N)r   r   r   r   s     r   test_format_srtzTestParseArgs.test_format_srtU   s)    !,,K
 {{e###r    N)__name__
__module____qualname__r   r   r(   r+   r.    r    r   r   r   /   s    .
%%&k
$r    r   c                       e Zd Zd Zd Zd Zy)TestDownloadAudioc                    |dz  }|j                  d       t        d      5 }t               }t        |      |j                  _        t        d      |j                  _        t        |dz        |j                  _        t        j                  dt        |            }ddd       t        t              sJ y# 1 sw Y   xY w)	u4   yt-dlp YoutubeDL 호출 및 WAV 파일 경로 반환	audio.wav   RIFF#youtube_transcribe.yt_dlp.YoutubeDLreturn_valueFz
audio.webmr   N)write_bytesr   r   r:   	__enter____exit__strprepare_filenamer   download_audio
isinstance)r   tmp_pathfake_wavmock_ydl_clsmock_ydlresults         r   test_calls_yt_dlpz#TestDownloadAudio.test_calls_yt_dlpc   s    k)W%89 	\ {H2;2RL%%/1:1NL%%. 69L9P5QH%%2 (6613x=F	 &#&&&	 	s   A=B::Cc                 ~   |dz  }|j                  d       t        d      5 }t               }|j                  }t        |      |_        t        d      |_        t        |dz        |j                  _        t        j                  dt        |            }ddd       j                  d	      sJ y# 1 sw Y   xY w)
u*   download_audio 반환값이 .wav 확장자zvideo_title.wavr7   r8   r9   Fzvideo_title.webmr   Nz.wav)r;   r   r   r:   r<   r=   r>   r?   r   r@   endswith)r   rB   rC   rD   rE   ctx_mgrrF   s          r   test_returns_wav_pathz'TestDownloadAudio.test_returns_wav_pathy   s    //W%89 		\ {H"//G )x @G(e<G58DV9V5WH%%2'6613x=F		 v&&&		 		s   A5B33B<c                    t        d      5 }t               }|j                  }t        |      |_        t        d      |_        t        d      |j                  _        t        j                  t
        d      5  t        j                  dt        |             ddd       ddd       y# 1 sw Y   xY w# 1 sw Y   yxY w)u,   yt-dlp 다운로드 실패 시 예외 발생r8   r9   Fzyt-dlp error)matchr   N)r   r   r:   r<   r=   	Exceptiondownloadside_effectr   r   r   r@   r>   )r   rB   rD   rE   rJ   s        r   test_raises_on_yt_dlp_errorz-TestDownloadAudio.test_raises_on_yt_dlp_error   s    89 
	\ {H"//G )x @G(e<G,5n,EH)y? "115s8}
	 
	 
	 
	s$   A.B7: B+B7+B4	0B77C N)r/   r0   r1   rG   rK   rQ   r2   r    r   r4   r4   b   s    ','$r    r4   c                   $    e Zd Zd Zd Zd Zd Zy)TestTranscribeAudioSuccessc                 (   |dz  }|j                  d       t               }d|_        dddddgd|j                  _        t        d	|
      5  t        j                  t        |      d      }ddd       d   dk(  sJ d|v sJ y# 1 sw Y   xY w)u   정상 응답 시 dict 반환r6   r7         안녕하세요              ?startendr   r   segments youtube_transcribe.requests.postr9   r   r   Nr   r]   )	r;   r   status_coder#   r:   r   r   transcribe_audior>   )r   rB   rC   mock_responserF   s        r   !test_returns_transcription_resultz<TestTranscribeAudioSuccess.test_returns_transcription_result   s    k)W%!$'!%#&s<MNO+
'
 5MR 	W'88XQU8VF	W f~!2222V###		W 	Ws   !BBc                    |dz  }|j                  d       t               }d|_        ddi|j                  _        t        d|      5 }t        j                  t        |      d	       d
d
d
       j                  }|j                  j                  d      (t        |j                        dkD  rdt        |      v sJ y
y
# 1 sw Y   YxY w)u6   requests.post에 files 인자(multipart) 전달 확인r6   r7   rU   r   testr^   r9   r   r_   Nfiles   )r;   r   r`   r#   r:   r   r   ra   r>   	call_argskwargsgetlenr   r   rB   rC   rb   	mock_postcall_kwargss         r   test_sends_multipart_form_dataz9TestTranscribeAudioSuccess.test_sends_multipart_form_data   s    k)W%!$'!+16*:'5MR 	NV_//H/M	N  ))!!%%g.:  !A%'S5E*E	
 
*E ;	N 	N   !CC
c                    |dz  }|j                  d       t               }d|_        ddi|j                  _        t        d|      5 }t        j                  t        |      d	       d
d
d
       j                  }|j                  r|j                  d   n|j                  j                  dd      }d|v sJ d|v sJ y
# 1 sw Y   YxY w)u/   로컬 Whisper GPU 서비스 URL로 POST 전송r6   r7   rU   r   re   r^   r9   r   r_   Nr   r    zlocalhost:8200z/v1/transcribe)r;   r   r`   r#   r:   r   r   ra   r>   rh   r   ri   rj   )r   rB   rC   rb   rm   rh   url_args          r   test_sends_correct_urlz1TestTranscribeAudioSuccess.test_sends_correct_url   s    k)W%!$'!+16*:'5MR 	NV_//H/M	N ''	'0~~)..#9;K;K;O;OPUWY;Z7***7***	N 	Nrp   c                 T   |dz  }|j                  d       t               }d|_        ddi|j                  _        t        d|      5 }t        j                  t        |      d	       d
d
d
       j                  j                  }|j                  d      dk(  sJ y
# 1 sw Y   6xY w)u   timeout=600 으로 POST 전송r6   r7   rU   r   re   r^   r9   r   r_   NtimeoutiX  )r;   r   r`   r#   r:   r   r   ra   r>   rh   ri   rj   rl   s         r   test_timeout_600_secondsz3TestTranscribeAudioSuccess.test_timeout_600_seconds   s    k)W%!$'!+16*:'5MR 	NV_//H/M	N  ))00y)S000		N 	Ns   !BB'N)r/   r0   r1   rc   ro   rt   rw   r2   r    r   rS   rS      s    $$
$+"1r    rS   c                       e Zd Zd Zd Zd Zy)TestTranscribeAudioFallbackc                 ~   ddl }|dz  }|j                  d       t        d|j                  j	                  d            5  t
        j                  t        |      d	      }ddd       d
   dk(  sJ |j                         }d|j                  v s d|j                  v sd|j                  v sJ yyy# 1 sw Y   RxY w)u8   ConnectionError 시 fallback dict 반환 + 경고 로그r   Nr6   r7   r^   refusedrP   r   r_   r   5   로컬 Whisper 서비스가 응답하지 않습니다   경고WARNINGWhisper)
requestsr;   r   
exceptionsConnectionErrorr   ra   r>   
readouterrerrr   rB   capsys
req_modulerC   rF   captureds          r   &test_connection_error_returns_fallbackzBTestTranscribeAudioFallback.test_connection_error_returns_fallback   s    %k)W%."--==iH
 	W (88XQU8VF		W f~!XXXX$$&8<<'9+D	U]UaUaHaaaHa+D'	W 	W   !B33B<c                 ~   ddl }|dz  }|j                  d       t        d|j                  j	                  d            5  t
        j                  t        |      d	      }ddd       d
   dk(  sJ |j                         }d|j                  v s d|j                  v sd|j                  v sJ yyy# 1 sw Y   RxY w)u0   Timeout 시 fallback dict 반환 + 경고 로그r   Nr6   r7   r^   rv   r|   r   r_   r   r}   r~   r   r   )
r   r;   r   r   Timeoutr   ra   r>   r   r   r   s          r   #test_timeout_error_returns_fallbackz?TestTranscribeAudioFallback.test_timeout_error_returns_fallback   s    %k)W%."--55i@
 	W (88XQU8VF		W f~!XXXX$$&8<<'9+D	U]UaUaHaaaHa+D'	W 	Wr   c                 &   ddl }|dz  }|j                  d       t        d|j                  j	                  d            5  t
        j                  t        |      d	      }ddd       j                  d
g       }|g k(  s|J yy# 1 sw Y   &xY w)u7   fallback 결과에 segments 키 없거나 빈 리스트r   Nr6   r7   r^   r{   r|   r   r_   r]   )	r   r;   r   r   r   r   ra   r>   rj   )r   rB   r   rC   rF   r]   s         r   test_fallback_has_no_segmentsz9TestTranscribeAudioFallback.test_fallback_has_no_segments  s    %k)W%."--==iH
 	W (88XQU8VF		W ::j"-2~!111!1~	W 	Ws   !BBN)r/   r0   r1   r   r   r   r2   r    r   ry   ry      s    b"b"2r    ry   c                   N    e Zd ZdddddddddgdZd	 Zd
 Zd Zd Zd Zd Z	y)TestFormatOutput   안녕하세요 반갑습니다rW   rX   rV   rY   g      @u   반갑습니다r\   c                 t    t         j                  | j                  d      }d|v sJ t        |t              sJ y)u&   text 포맷은 순수 텍스트 반환r   r   N)r   format_outputSAMPLE_RESULTrA   r>   r   r   s     r   test_text_formatz!TestFormatOutput.test_text_format,  s8    #11$2D2DfM0F:::&#&&&r    c                     t         j                  | j                  d      }t        j                  |      }|d   dk(  sJ t        |d         dk(  sJ y)u5   json 포맷은 파싱 가능한 JSON 문자열 반환r#   r   r   r]      N)r   r   r   r#   loadsrk   )r   r   parseds      r   test_json_formatz!TestFormatOutput.test_json_format2  sS    #11$2D2DfMF#f~!BBBB6*%&!+++r    c                 h    t         j                  | j                  d      }d|v sJ d|v sJ d|v sJ y)u)   srt 포맷은 SRT 형식 문자열 반환r-   1z-->rV   Nr   r   r   r   s     r   test_srt_formatz TestFormatOutput.test_srt_format9  s@    #11$2D2DeLf}} F***r    c                 \    t         j                  | j                  d      }d|v sJ d|v sJ y)u8   srt 타임코드가 HH:MM:SS,mmm --> HH:MM:SS,mmm 형식r-   z00:00:00,000z00:00:01,500Nr   r   s     r   test_srt_timecode_formatz)TestFormatOutput.test_srt_timecode_formatA  s6    #11$2D2DeL''''''r    c                 D    ddi}t         j                  |d      }d|v sJ y)u0   segments 없는 경우 text 포맷 정상 동작r   u	   테스트N)r   r   )r   rF   r   s      r   !test_text_format_without_segmentsz2TestFormatOutput.test_text_format_without_segmentsH  s,    +&#11&&Af$$$r    c                 v    dg d}t         j                  |d      }t        j                  |      }d|d   v sJ y)u1   fallback 메시지도 json으로 직렬화 가능r}   r\   r#   r   r   N)r   r   r#   r   )r   rF   r   r   s       r   !test_json_format_fallback_messagez2TestFormatOutput.test_json_format_fallback_messageN  s?    Q_ab#11&&AF#F6N***r    N)
r/   r0   r1   r   r   r   r   r   r   r   r2   r    r   r   r   #  sE    1#/@A#/@A
M',+(%+r    r   c                   0    e Zd Zd Zd Zd Zd Zd Zd Zy)TestMainSuccessc                     dddddgdS )N   테스트 전사 결과rW   g       @rY   r\   r2   r   s    r   _make_mock_resultz!TestMainSuccess._make_mock_result\  s    -#&s<UVW
 	
r    c           	         t        dd      5  t        d| j                               5  t        dd      5  t        d      5  t        j                  g d       d	d	d	       d	d	d	       d	d	d	       d	d	d	       |j	                         }d
|j
                  v sJ y	# 1 sw Y   BxY w# 1 sw Y   FxY w# 1 sw Y   JxY w# 1 sw Y   NxY w)u.   정상 흐름: text 포맷으로 stdout 출력!youtube_transcribe.download_audio/tmp/audio.wavr9   #youtube_transcribe.transcribe_audio#youtube_transcribe.tempfile.mkdtemp/tmp/yt_test youtube_transcribe.shutil.rmtreer   r   r"   r   Nr   )r   r   r   mainr   out)r   r   r   s      r   test_main_text_to_stdoutz(TestMainSuccess.test_main_text_to_stdoutb  s    6EUV 	X]1@V@V@XY
 	6^T	V[.W
	
 ##P	 	 	 	 $$&(HLL888	 	 	 	 	 	 	 	sQ   CB5B)B	B)$B5,CB&"B))B2.B55B>	:CC
c           	         t        dd      5  t        d| j                               5  t        dd      5  t        d      5  t        j                  g d       d	d	d	       d	d	d	       d	d	d	       d	d	d	       |j	                         }t        j                  |j                        }|d
   dk(  sJ y	# 1 sw Y   [xY w# 1 sw Y   _xY w# 1 sw Y   cxY w# 1 sw Y   gxY w)u   json 포맷으로 stdout 출력r   r   r9   r   r   r   r   )r   r   r"   r#   Nr   r   )r   r   r   r   r   r#   r   r   )r   r   r   r   s       r   test_main_json_to_stdoutz(TestMainSuccess.test_main_json_to_stdoutp  s    6EUV 	X]1@V@V@XY
 	6^T	V[.W
	
 ##P	 	 	 	 $$&HLL)f~!::::	 	 	 	 	 	 	 	sQ   CCCB6	C$C,C6B?;CCCC	CC#c                    |dz  }t        dd      5  t        d| j                               5  t        dd      5  t        d      5  t        j                  d	d
dddt	        |      g       ddd       ddd       ddd       ddd       |j                         sJ |j                  d      }d|v sJ y# 1 sw Y   LxY w# 1 sw Y   PxY w# 1 sw Y   TxY w# 1 sw Y   XxY w)u$   --output 지정 시 파일에 저장z
result.txtr   r   r9   r   r   r   r   r   r   r"   r   r$   Nzutf-8)encodingr   )r   r   r   r   r>   exists	read_text)r   rB   out_filecontents       r   test_main_output_to_filez(TestMainSuccess.test_main_output_to_file  s    l*6EUV 	X]1@V@V@XY
 	6^T	V[.W
	
 ##5M		 	 	 	     $$g$6(G333%	 	 	 	 	 	 	 	sQ   CCC	%B9	.C6C>C9C>CC
CC	CC&c           	         t        dd      5  t        d| j                               5  t        dd      5  t        d      5 }t        j                  dd	g       d
d
d
       d
d
d
       d
d
d
       d
d
d
       j	                  dd       y
# 1 sw Y   5xY w# 1 sw Y   9xY w# 1 sw Y   =xY w# 1 sw Y   AxY w)u    임시 디렉토리가 정리됨r   r   r9   r   r   z/tmp/yt_test_cleanupr   r   r   NTignore_errors)r   r   r   r   assert_called_once_withr   mock_rmtrees     r   test_main_cleans_temp_dirz)TestMainSuccess.test_main_cleans_temp_dir  s    6EUV 		X]1@V@V@XY
 		1@V
		 .
			 ##;<		 		 		 		 	++,BRV+W		 		 		 		 		 		 		 		sQ   B4B(BB	B$B(,B4BBB%!B((B1	-B44B=c           	      (   t        dd      5  t        d| j                               5 }t        dd      5  t        d      5  t        j                  g d       d	d	d	       d	d	d	       d	d	d	       d	d	d	       j	                          |j
                  }|j                  j                  d
      dk(  s-t        |j                        dkD  r|j                  d   dk(  sJ y	y	# 1 sw Y   xY w# 1 sw Y   xY w# 1 sw Y   xY w# 1 sw Y   xY w)u2   --language 인자가 transcribe_audio에 전달됨r   r   r9   r   r   r   r   )r   r   r&   r'   Nr   r'   rg   )
r   r   r   r   assert_called_oncerh   ri   rj   rk   r   )r   mock_transcribern   s      r   'test_main_passes_language_to_transcribez7TestMainSuccess.test_main_passes_language_to_transcribe  s   6EUV 		X]1@V@V@XY
 		e1
		 .
			 ##P		 		 		 		 	**,%//!!%%j1T9  !A%+*:*:1*=*E	
 
*E :		 		 		 		 		 		 		 		sQ   DC<C0C$	C0$C<,D$C-)C00C95C<<D	DDN)	r/   r0   r1   r   r   r   r   r   r   r2   r    r   r   r   [  s!    
9;40X
r    r   c                       e Zd Zd Zy)TestMainFallbackc           	         dg d}t        dd      5  t        d|      5  t        dd      5  t        d	      5  t        j                  g d
       ddd       ddd       ddd       ddd       |j                         }d|j                  v sJ y# 1 sw Y   BxY w# 1 sw Y   FxY w# 1 sw Y   JxY w# 1 sw Y   NxY w)u.   서비스 불가 시 fallback 메시지 출력r}   r\   r   r   r9   r   r   r   r   r   N)r   r   r   r   r   )r   r   fallback_resultr   s       r   test_fallback_message_in_outputz0TestMainFallback.test_fallback_message_in_output  s     L

 6EUV 	X]1Y
 	6^T	V[.W
	
 ##P	 	 	 	 $$&F(,,VVV	 	 	 	 	 	 	 	sP   B8B,B B	B B,#B8BB  B)%B,,B5	1B88CN)r/   r0   r1   r   r2   r    r   r   r     s    Wr    r   c                       e Zd Zd Zd Zd Zy)TestExitCodec           	         dg d}t        dd      5  t        d|      5  t        dd      5  t        d	      5  	 t        j                  d
dg       ddd       ddd       ddd       ddd       y# t        $ r-}|j                  dk(  sJ d|j                          Y d}~Rd}~ww xY w# 1 sw Y   [xY w# 1 sw Y   _xY w# 1 sw Y   cxY w# 1 sw Y   yxY w)u8   성공 시 SystemExit 없이 정상 종료 (exit code 0)u   성공r\   r   r   r9   r   r   r   r   r   r   r   zExpected exit code 0, got N)r   r   r   r   code)r   mock_resultes      r   test_exit_code_0_on_successz(TestExitCode.test_exit_code_0_on_success  s    'R86EUV 		JX]1Y
 		J6^T		JV[.W
		JJ"''2S(TU		J 		J 		J 		J 		J  Jvv{I&@$II{J		J 		J 		J 		J 		J 		J 		J 		Jss   CCB:B.	A5B:C$C5	B+>#B&!B.	&B++B.	.B73B::C?CC	CCc           	         t        dt        d            5  t        dd      5  t        d      5  t        j                  t              5 }t
        j                  dd	g       d
d
d
       d
d
d
       d
d
d
       d
d
d
       j                  j                  dk(  sJ y
# 1 sw Y   =xY w# 1 sw Y   AxY w# 1 sw Y   ExY w# 1 sw Y   IxY w)u   yt-dlp 실패 시 SystemExit(1)r   zyt-dlp failedr|   r   r   r9   r   r   r   Nrg   )	r   rN   r   r   r   r   r   valuer   )r   exc_infos     r   "test_exit_code_1_on_download_errorz/TestExitCode.test_exit_code_1_on_download_error  s    /Y=W
 	V6^T	VV[.W
	V
 z* Vh"''2S(TUV	V 	V 	V ~~""a'''V V	V 	V 	V 	V 	V 	VsQ   CB7B+B	#B++B73CB($B++B40B77C 	<CCc           	         t        dt        d            5  t        dd      5  t        d      5 }t        j                  t              5  t
        j                  dd	g       d
d
d
       d
d
d
       d
d
d
       d
d
d
       j                  dd       y
# 1 sw Y   5xY w# 1 sw Y   9xY w# 1 sw Y   =xY w# 1 sw Y   AxY w)u2   에러 발생 시에도 임시 디렉토리 정리r   zdownload errorr|   r   z/tmp/yt_test_errr9   r   r   r   NTr   )r   rN   r   r   r   r   r   r   r   s     r   #test_temp_dir_cleaned_even_on_errorz0TestExitCode.test_temp_dir_cleaned_even_on_error  s    /YGW=X
 	V1@R
	V .
		V z* V"''2S(TUV	V 	V 	V 	++,>d+SV V	V 	V 	V 	V 	V 	VsQ   B;B/B#B	#B#+B/3B;B B##B,(B//B8	4B;;CN)r/   r0   r1   r   r   r   r2   r    r   r   r     s    J
(Tr    r   )"__doc__importlib.util	importlibr#   syspathlibr   unittest.mockr   r   r   __file__parent_SCRIPTS_DIRpathinsertr>   _MODULE_PATHutilspec_from_file_locationspecmodule_from_specr   loadermodulesexec_moduler   r4   rS   ry   r   r   r   r   r2   r    r   <module>r      s!  $   
  *  H~$$++ 3|$ % 55~~--.BLQ ^^44T: {{ $6  !   * ++$ +$f5 5zC1 C1V12 12r0+ 0+p\
 \
HW W4(T (Tr    