
    (<iM3                       d 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
mZ ddlZej                  j                  d e ee      j"                                G d d      Z G d	 d
      Z G d d      Z G d d      Z G d d      Zy)u]   gcloud_auth 모듈의 단위 테스트.

TDD RED 단계: 구현 전 테스트 먼저 작성.
    )annotationsN)Path)Any)	MagicMockpatchc                  4    e Zd ZddZddZddZddZddZy)TestTokenCachingc                T   ddl }t               }d|_        d|_        d|_        t        d|df      5  t        j                  |d	ddd
      5  |j                         }ddd       ddd       t        t              sJ t        |      dkD  sJ y# 1 sw Y   4xY w# 1 sw Y   8xY w)u7   get_access_token()은 문자열 토큰을 반환한다.r   Nzcached-token-abcTFgoogle.auth.default
project-idreturn_value_token_cachetokenexpiry)gcloud_authr   r   validexpiredr   objectget_access_token
isinstancestrlen)selfr   
mock_credsr   s       T/home/jay/workspace/.worktrees/task-2057-dev2/tools/ai-image-gen/test_gcloud_auth.py$test_get_access_token_returns_stringz5TestTokenCaching.test_get_access_token_returns_string   s    [
-

"
(
L7QR 	7k>TUY;Z[ 7#4467	7 %%%%5zA~~	7 7	7 	7s#   BBBB	BB'c                &   ddl }t        j                         dz   }t        j                  |dd|d      5  t        d      5 }|j	                         }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)uC   유효한 캐시 토큰이 있으면 재획득 없이 반환한다.r   Ni  r   zalready-cached-tokenr   r   r   timer   r   r   assert_not_called)r   r   future_expirymock_adcr   s        r   test_cached_token_is_reusedz,TestTokenCaching.test_cached_token_is_reused)   s    		d*\\,F
 	7
 ,- 7#4467	7 	""$....7 7	7 	7#   BA;B;B	 BBc                   ddl }t        j                         dz
  }t               }d|_        d|_        d|_        t        j                  |dd|d	      5  t        d
|df      5  t        d      5  |j                         }ddd       ddd       ddd       dk(  sJ y# 1 sw Y   !xY w# 1 sw Y   %xY w# 1 sw Y   )xY w)u6   만료된 캐시 토큰은 새 토큰을 획득한다.r   N
   znew-token-after-expiryTFr   zold-expired-tokenr   r   r   r   &google.auth.transport.requests.Request	r   r!   r   r   r   r   r   r   r   )r   r   past_expiryr   r   s        r   #test_expired_cache_triggers_renewalz4TestTokenCaching.test_expired_cache_triggers_renewal:   s    iikB&[
3

"
\\)[A
 	;
 ,J;UV ;CD ;'88:E;;	; 0000; ;; ;	; 	;<   B:%B.1B"B.
B:"B+'B..B7	3B::Cc                   ddl }t        j                         dz   }t               }d|_        d|_        d|_        t        j                  |dd|d	      5  t        d
|df      5  t        d      5  |j                         }ddd       ddd       ddd       dk(  sJ y# 1 sw Y   !xY w# 1 sw Y   %xY w# 1 sw Y   )xY w)u1   만료 5분 이내 토큰은 미리 갱신한다.r   N   zrefreshed-tokenTFr   zsoon-expiring-tokenr   r   r   r   r)   r*   )r   r   near_expiryr   r   s        r   'test_token_near_expiry_triggers_renewalz8TestTokenCaching.test_token_near_expiry_triggers_renewalP   s    iikC'[
,

"
\\+{C
 	;
 ,J;UV ;CD ;'88:E;;	; )))); ;; ;	; 	;r-   c                &   ddl }t        j                         dz   }t        j                  |dd|d      5  t        d      5 }|j	                         }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)u;   만료 5분 이상 남은 토큰은 갱신하지 않는다.r   NiX  r   zvalid-token-10minr   r   r    )r   r   safe_expiryr$   r   s        r   ,test_valid_cache_not_near_expiry_not_renewedz=TestTokenCaching.test_valid_cache_not_near_expiry_not_renewedf   s    iikC'\\)[A
 	7
 ,- 7#4467	7 	""$++++	7 7	7 	7r&   NreturnNone)__name__
__module____qualname__r   r%   r,   r1   r4        r   r	   r	      s     /"1,*,,r<   r	   c                  ,    e Zd ZddZddZddZddZy)TestGcloudFallbackc           	        ddl }t        j                  |dddd      5  t        j                  |dt        d            5  t        dt	        d	            5  t               }d
|_        t        d|      5  |j                         }ddd       ddd       ddd       ddd       dk(  sJ y# 1 sw Y   )xY w# 1 sw Y   -xY w# 1 sw Y   1xY w# 1 sw Y   5xY w)uG   SA 및 ADC 실패 시 gcloud CLI fallback으로 토큰을 획득한다.r   Nr   r   get_service_account_token	   SA 없음side_effectr   u   ADC 설정 없음zgcloud-fallback-token
subprocess.runr   zgcloud-fallback-token)r   r   r   RuntimeError	Exceptionr   stdoutr   )r   r   mock_resultr   s       r   (test_falls_back_to_gcloud_when_adc_failsz;TestGcloudFallback.test_falls_back_to_gcloud_when_adc_fails~   s    \\+~QU7VW 	?k+FT`alTmn ?0iH[>\] ?"++K)BK&/kJ ? + < < >???	? ////? ?? ?? ?	? 	?sS   "CCB<7B0	B<CC0B95B<<CCC	CCc           
        ddl }t        j                  |dddd      5  t        j                  |dt        d            5  t        dt	        d	            5  t               }d
|_        t        d|      5  t        j                  t        d      5  |j                          ddd       ddd       ddd       ddd       ddd       y# 1 sw Y   *xY w# 1 sw Y   .xY w# 1 sw Y   2xY w# 1 sw Y   6xY w# 1 sw Y   yxY w)uQ   gcloud fallback에서 빈 토큰이 반환되면 RuntimeError를 발생시킨다.r   Nr   r   r@   rA   rB   r   
   ADC 없음z   
rD   r   u
   빈 토큰)match)
r   r   r   rE   rF   r   rG   pytestraisesr   )r   r   rH   s      r   ,test_gcloud_fallback_raises_when_empty_tokenz?TestGcloudFallback.test_gcloud_fallback_raises_when_empty_token   s    \\+~QU7VW 	;k+FT`alTmn ;0i>UV ;"++K)0K&/kJ ;#]]<|L ;'88:;;;;	; 	;; ;; ;; ;; ;	; 	;sk   "C=C1C%7C	C$C	,C%4C1<C=CC	C"C%%C.*C11C:	6C==Dc           
        ddl }t        j                  |dddd      5  t        j                  |dt        d            5  t        dt	        d	            5  t        d
t	        d            5  t        j                  t              5  |j                          ddd       ddd       ddd       ddd       ddd       y# 1 sw Y   *xY w# 1 sw Y   .xY w# 1 sw Y   2xY w# 1 sw Y   6xY w# 1 sw Y   yxY w)uH   SA, ADC, gcloud CLI 모두 실패하면 RuntimeError를 발생시킨다.r   Nr   r   r@   rA   rB   r   rK   rD   u   gcloud 없음)r   r   r   rE   rF   rM   rN   r   r   r   s     r   )test_gcloud_fallback_raises_when_all_failz<TestGcloudFallback.test_gcloud_fallback_raises_when_all_fail   s    \\+~QU7VW 	;k+FT`alTmn ;0i>UV ;/Y=WX ;#]]<8 ;'88:;;;;	; 	;; ;; ;; ;; ;	; 	;sk   "C3C'C/C		CC	"C*C'2C3CC	CCC$ C''C0	,C33C<c                   ddl }t               }d|_        d|_        d|_        t        j                  |dddd      5  t        d|d	f
      5  t               }t        d|
      5  |j                         }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   @xY w# 1 sw Y   DxY w)u=   ADC 크레덴셜이 만료된 경우 refresh를 호출한다.r   NFTzrefreshed-adc-tokenr   r   r   r   r   r)   )
r   r   r   r   r   r   r   r   refreshassert_called_once_with)r   r   r   mock_requestr   s        r   #test_adc_token_refreshed_if_expiredz6TestGcloudFallback.test_adc_token_refreshed_if_expired   s    [
 
!
0
\\+~QU7VW 	;,J;UV ;({<!- ; (88:E	;;	; 	22<@----; ;; ;	; 	;s;   C
B>&B27B>?C
2B;7B>>C	C

CNr5   )r8   r9   r:   rI   rO   rR   rW   r;   r<   r   r>   r>   }   s    0;	;.r<   r>   c                  8    e Zd ZddZddZ	 	 	 	 	 	 ddZddZy)TestEnvVarLoadingc                    ddl }|dz  }d}|j                  d| dd       |j                  d	d
       |j                  t	        |             ddl}|j                  j                  d	      |k(  sJ y)u?   GOOGLE_APPLICATION_CREDENTIALS를 .env.keys에서 로드한다.r   N	.env.keysz/fake/path/service-account.jsonz'export GOOGLE_APPLICATION_CREDENTIALS="z"
utf-8encodingGOOGLE_APPLICATION_CREDENTIALSFraisingr   
write_textdelenvload_env_keysr   osenvirongetr   tmp_pathmonkeypatchr   fake_envfake_creds_pathrf   s          r   $test_load_env_keys_sets_google_credsz6TestEnvVarLoading.test_load_env_keys_sets_google_creds   s~    k);5o5FcJ 	 	

 	;UK!!#h-0zz~~>??RRRr<   c                .    ddl }|j                  d       y)u?   존재하지 않는 .env.keys 파일은 조용히 무시한다.r   Nz/nonexistent/path/.env.keys)r   re   rQ   s     r   %test_load_env_keys_skips_missing_filez7TestEnvVarLoading.test_load_env_keys_skips_missing_file   s     	!!"?@r<   c                    ddl }|dz  }|j                  dd       |j                  dd	       |j                  t	        |             y)
uN   GOOGLE_APPLICATION_CREDENTIALS가 없는 파일도 오류 없이 처리한다.r   Nr[   zexport OTHER_VAR=some_value
r\   r]   r_   Fr`   )r   rc   rd   re   r   )r   rj   rk   r   rl   s        r   /test_load_env_keys_handles_no_google_creds_linezATestEnvVarLoading.test_load_env_keys_handles_no_google_creds_line   sK     	k);gN;UK!!#h-0r<   c                    ddl }|dz  }d}|j                  d| dd       |j                  d	d
       |j                  t	        |             ddl}|j                  j                  d	      |k(  sJ y)uD   따옴표 없는 GOOGLE_APPLICATION_CREDENTIALS 값도 파싱한다.r   Nr[   z/another/path/creds.jsonz&export GOOGLE_APPLICATION_CREDENTIALS=
r\   r]   r_   Fr`   rb   ri   s          r   !test_load_env_keys_without_quotesz3TestEnvVarLoading.test_load_env_keys_without_quotes   s~    k)44_4ERH 	 	

 	;UK!!#h-0zz~~>??RRRr<   N)rj   r   rk   r   r6   r7   r5   )r8   r9   r:   rn   rp   rr   ru   r;   r<   r   rY   rY      s1    S$A
1
1+.
1	
1Sr<   rY   c                  $    e Zd ZddZddZddZy)TestLoggingc           	        ddl }ddl}t               }d|_        d|_        d|_        t        j                  |dddd      5  t        d|d	f
      5  t        d      5  t        j                  |j                  d      5 }|j                          ddd       ddd       ddd       ddd       j                  sJ y# 1 sw Y   0xY w# 1 sw Y   4xY w# 1 sw Y   8xY w# 1 sw Y   <xY w)u0   ADC 성공 시 로그 메시지가 기록된다.r   Nzlog-test-tokenTFr   r   r   r   r   r)   info)r   loggingr   r   r   r   r   r   loggerr   called)r   r   rz   r   mock_logs        r   )test_get_access_token_logs_on_adc_successz5TestLogging.test_get_access_token_logs_on_adc_success   s    [
+

"
\\+~QU7VW 	7,J;UV 7CD 7k&8&8&A 7X#446777	7 7 77 77 7	7 	7sT   C#C!C?B?	CC C#?CCCCC 	C##C,c           
     8   ddl }t        j                  |dddd      5  t        j                  |dt        d            5  t        dt	        d	            5  t               }d
|_        t        d|      5  t        j                  |j                  d      5 }|j                          ddd       ddd       ddd       ddd       ddd       j                  sJ y# 1 sw Y   8xY w# 1 sw Y   <xY w# 1 sw Y   @xY w# 1 sw Y   DxY w# 1 sw Y   HxY w)u4   SA 및 ADC 실패 시 경고 로그가 기록된다.r   Nr   r   r@   rA   rB   r   rK   zfallback-token
rD   r   warning)
r   r   r   rE   rF   r   rG   r{   r   r|   )r   r   rH   	mock_warns       r   1test_get_access_token_logs_warning_on_adc_failurez=TestLogging.test_get_access_token_logs_warning_on_adc_failure  s    \\+~QU7VW 	;k+FT`alTmn ;0i>UV ;"++K);K&/kJ ;"\\+*<*<iH ;I'88:;;;;	; ; ;; ;; ;; ;	; 	;sk   "DDC87!C,	C )C,	1C89DD C)%C,	,C51C88D=DD		DDc                B    ddl }|j                  j                  dk(  sJ y)u%   logger 이름이 'gcloud_auth'이다.r   Nr   )r   r{   namerQ   s     r    test_logger_is_named_gcloud_authz,TestLogging.test_logger_is_named_gcloud_auth  s    !!&&-777r<   Nr5   )r8   r9   r:   r~   r   r   r;   r<   r   rw   rw      s    $ 8r<   rw   c                  $    e Zd ZddZddZddZy)TestTokenCacheStructurec                L    ddl }d|j                  v sJ d|j                  v sJ y)u2   _token_cache에 'token'과 'expiry' 키가 있다.r   Nr   r   )r   r   rQ   s     r   "test_token_cache_has_required_keysz:TestTokenCacheStructure.test_token_cache_has_required_keys,  s-    +22222;33333r<   c                T    ddl }|j                  d   }|t        |t              sJ yy)uO   모듈 초기 상태에서 _token_cache.token은 None이거나 문자열이다.r   Nr   )r   r   r   r   )r   r   cache_tokens      r    test_token_cache_initially_emptyz8TestTokenCacheStructure.test_token_cache_initially_empty3  s0    !..w7"jc&BBB&B"r<   c                   ddl }t               }d|_        d|_        d|_        t        j                  |dddd      5  t        d|d	f
      5  t        d      5  |j                          ddd       ddd       |j                  d   dk(  sJ |j                  d   J 	 ddd       y# 1 sw Y   @xY w# 1 sw Y   DxY w# 1 sw Y   yxY w)uE   get_access_token() 성공 후 _token_cache에 토큰이 저장된다.r   Nzcache-update-tokenTFr   r   r   r   r   r)   r   r   )	r   r   r   r   r   r   r   r   r   )r   r   r   s      r   #test_get_access_token_updates_cachez;TestTokenCacheStructure.test_get_access_token_updates_cache:  s    [
/

"
\\+~QU7VW 	B,J;UV 3CD 300233 ++G48LLLL++H5AAA	B 	B3 33 3	B 	Bs;   CB6B*+B63-C*B3/B66B?	;CCNr5   )r8   r9   r:   r   r   r   r;   r<   r   r   r   +  s    4CBr<   r   )__doc__
__future__r   sysr!   pathlibr   typingr   unittest.mockr   r   rM   pathinsertr   __file__parentr	   r>   rY   rw   r   r;   r<   r   <module>r      s~   
 # 
    *  3tH~,,- .], ],J:. :.D6S 6S|&8 &8\B Br<   