
    QpiM3                    6   d Z ddlmZ ddlZddlmc 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ddt        j                         v st        j                  t              rt        j                  t              nddt        j                         v st        j                  |      rt        j                  |      nddt        j                         v st        j                  t              rt        j                  t              ndt        j                  |      dz  }t        t        j                   |            d}t#        |      }d}||kD  }|st        j$                  d|fd||f      dt        j                         v st        j                  t"              rt        j                  t"              nddt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      dz  }	dd|	iz  }
t        t        j                   |
            dx}x}}y# 1 sw Y   CxY w# 1 sw Y   HxY w)u7   get_access_token()은 문자열 토큰을 반환한다.r   Nzcached-token-abcTFgoogle.auth.default
project-idreturn_value_token_cachetokenexpiryz5assert %(py4)s
{%(py4)s = %(py0)s(%(py1)s, %(py2)s)
}
isinstancer   str)py0py1py2py4)>)z/%(py3)s
{%(py3)s = %(py0)s(%(py1)s)
} > %(py6)slen)r   r   py3py6zassert %(py8)spy8)gcloud_authr   r   validexpiredr   objectget_access_tokenr   r   @py_builtinslocals
@pytest_ar_should_repr_global_name	_safereprAssertionError_format_explanationr   _call_reprcompare)selfr   
mock_credsr   @py_assert3@py_format5@py_assert2@py_assert5@py_assert4@py_format7@py_format9s              :/home/jay/workspace/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 %%%%%%%%%z%%%z%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%5zAzA~zAss55zA	7 7	7 	7s#   J-J J- J*	%J--J7c                d   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(  }|st        j                  d|fd	||f      d
t        j                         v st        j                  |      rt        j                  |      nd
t        j                  |      dz  }dd|iz  }t        t        j                  |            dx}}y# 1 sw Y   xY w# 1 sw Y   xY w)uC   유효한 캐시 토큰이 있으면 재획득 없이 반환한다.r   Ni  r   zalready-cached-tokenr   r   ==z%(py0)s == %(py3)sr   r   r   assert %(py5)spy5r   timer   r!   r"   assert_not_calledr%   r*   r#   r$   r&   r'   r(   r)   )	r+   r   future_expirymock_adcr   r/   @py_assert1@py_format4@py_format6s	            r4   test_cached_token_is_reusedz,TestTokenCaching.test_cached_token_is_reused)   s    		d*\\,F
 	7
 ,- 7#4467	7 	""$..u.....u.......u...u...........7 7	7 	7#   D&DD&D#	D&&D/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(  }|st        j                  d|fd||f      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      dz  }dd|iz  }t        t        j                   |            dx}}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.Requestr7   r9   r   r:   r;   r<   r   r>   r   r   r   r    r   r!   r"   r%   r*   r#   r$   r&   r'   r(   r)   )	r+   r   past_expiryr,   r   r/   rB   rC   rD   s	            r4   #test_expired_cache_triggers_renewalz4TestTokenCaching.test_expired_cache_triggers_renewal:   s   iikB&[
3

"
\\)[A
 	;
 ,J;UV ;CD ;'88:E;;	; 10u00000u0000000u000u00000000000; ;; ;	; 	;<   E%E1EE
EE
EE	EE"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(  }|st        j                  d|fd||f      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      dz  }dd|iz  }t        t        j                   |            dx}}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   rI   r7   r9   r   r:   r;   r<   rJ   )	r+   r   near_expiryr,   r   r/   rB   rC   rD   s	            r4   'test_token_near_expiry_triggers_renewalz8TestTokenCaching.test_token_near_expiry_triggers_renewalP   s   iikC'[
,

"
\\+{C
 	;
 ,J;UV ;CD ;'88:E;;	; *)u)))))u)))))))u)))u))))))))))); ;; ;	; 	;rM   c                d   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(  }|st        j                  d|fd	||f      d
t        j                         v st        j                  |      rt        j                  |      nd
t        j                  |      dz  }dd|iz  }t        t        j                  |            dx}}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   r7   r9   r   r:   r;   r<   r=   )	r+   r   safe_expiryrA   r   r/   rB   rC   rD   s	            r4   ,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 	""$++u+++++u+++++++u+++u+++++++++++	7 7	7 	7rF   NreturnNone)__name__
__module____qualname__r5   rE   rL   rQ   rT        r4   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(  }|st        j                  d|fd||f      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      dz  }dd|iz  }t        t        j                  |            dx}}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)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-tokenr7   r9   r   r:   r;   r<   )r   r   r!   RuntimeError	Exceptionr   stdoutr"   r%   r*   r#   r$   r&   r'   r(   r)   )r+   r   mock_resultr   r/   rB   rC   rD   s           r4   (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 ? + < < >???	? 0/u/////u///////u///u///////////? ?? ?? ?	? 	?sS   "E3E'E7E	EE'E3EEE$ E''E0	,E33E<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      r4   ,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     r4   )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(  }|st        j                  d|fd||f      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      dz  }dd|iz  }t!        t        j"                  |            dx}}y# 1 sw Y   xY w# 1 sw Y   xY w# 1 sw Y   xY w)u=   ADC 크레덴셜이 만료된 경우 refresh를 호출한다.r   NFTzrefreshed-adc-tokenr   r   r   r   r   rI   r7   r9   r   r:   r;   r<   )r   r   r   r    r   r   r!   r"   refreshassert_called_once_withr%   r*   r#   r$   r&   r'   r(   r)   )	r+   r   r,   mock_requestr   r/   rB   rC   rD   s	            r4   #test_adc_token_refreshed_if_expiredz6TestGcloudFallback.test_adc_token_refreshed_if_expired   s    [
 
!
0
\\+~QU7VW 	;,J;UV ;({<!- ; (88:E	;;	; 	22<@--u-----u-------u---u-----------; ;; ;	; 	;s;   E)E&E7E?E)EEE&	"E))E2NrU   )rX   rY   rZ   ri   ro   rr   rw   r[   r\   r4   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t        j                  d|fd|
|f      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      t        j                  |	      t        j                  |
      dt        j                         v st        j                  |      rt        j                  |      nddz  }dd|iz  }t        t        j                  |            dx}x}x}	x}
}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raisingr7   zg%(py8)s
{%(py8)s = %(py4)s
{%(py4)s = %(py2)s
{%(py2)s = %(py0)s.environ
}.get
}(%(py6)s)
} == %(py10)sosfake_creds_pathr   r   r   r   r   py10assert %(py12)spy12r   
write_textdelenvload_env_keysr   r   environgetr%   r*   r#   r$   r&   r'   r(   r)   r+   tmp_pathmonkeypatchr   fake_envr   r   rB   r-   r0   @py_assert7@py_assert9@py_format11@py_format13s                 r4   $test_load_env_keys_sets_google_credsz6TestEnvVarLoading.test_load_env_keys_sets_google_creds   s    k);5o5FcJ 	 	

 	;UK!!#h-0zzRz~~R>R~>?R??RRRR??RRRRRRrRRRrRRRzRRR~RRR>RRR?RRRRRR?RRR?RRRRRRRRr\   c                .    ddl }|j                  d       y)u?   존재하지 않는 .env.keys 파일은 조용히 무시한다.r   Nz/nonexistent/path/.env.keys)r   r   rq   s     r4   %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   r   r   r   r   )r+   r   r   r   r   s        r4   /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t        j                  d|fd|
|f      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      t        j                  |	      t        j                  |
      dt        j                         v st        j                  |      rt        j                  |      nddz  }dd|iz  }t        t        j                  |            dx}x}x}	x}
}y)uD   따옴표 없는 GOOGLE_APPLICATION_CREDENTIALS 값도 파싱한다.r   Nr{   z/another/path/creds.jsonz&export GOOGLE_APPLICATION_CREDENTIALS=
r|   r}   r   Fr   r7   r   r   r   r   r   r   r   r   s                 r4   !test_load_env_keys_without_quotesz3TestEnvVarLoading.test_load_env_keys_without_quotes   s    k)44_4ERH 	 	

 	;UK!!#h-0zzRz~~R>R~>?R??RRRR??RRRRRRrRRRrRRRzRRR~RRR>RRR?RRRRRR?RRR?RRRRRRRRr\   N)r   r   r   r   rV   rW   rU   )rX   rY   rZ   r   r   r   r   r[   r\   r4   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                  }|syddt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      dz  }t!        t        j"                  |            d}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)u0   ADC 성공 시 로그 메시지가 기록된다.r   Nzlog-test-tokenTFr   r   r   r   r   rI   info*assert %(py2)s
{%(py2)s = %(py0)s.called
}mock_logr   r   )r   loggingr   r   r   r    r   r!   loggerr"   calledr#   r$   r%   r&   r'   r(   r)   )r+   r   r   r,   r   rB   @py_format3s          r4   )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 xx7 77 77 7	7 	7sT   EE!E?D:	EE E:E?EEEE	EE'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                  |j                  d      5 }|j                          ddd       ddd       ddd       ddd       ddd       j                  }|syddt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      dz  }t        t        j                   |            d}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# 1 sw Y   xY w)u4   SA 및 ADC 실패 시 경고 로그가 기록된다.r   Nr   r   r`   ra   rb   r   rk   zfallback-token
rd   r   warningr   	mock_warnr   )r   r   r!   re   rf   r   rg   r   r"   r   r#   r$   r%   r&   r'   r(   r)   )r+   r   rh   r   rB   r   s         r4   1test_get_access_token_logs_warning_on_adc_failurez=TestLogging.test_get_access_token_logs_warning_on_adc_failure  s3   \\+~QU7VW 	;k+FT`alTmn ;0i>UV ;"++K);K&/kJ ;"\\+*<*<iH ;I'88:;;;;	; yy; ;; ;; ;; ;	; 	;sk   "FE?E37!E'	E)E'	1E39E?FE$ E'	'E0,E33E<8E??F	FFc                   ddl }|j                  }|j                  }d}||k(  }|st        j                  d|fd||f      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      t        j                  |      dz  }dd|iz  }t        t        j                  |            dx}x}x}}y)	u%   logger 이름이 'gcloud_auth'이다.r   Nr   r7   )zH%(py4)s
{%(py4)s = %(py2)s
{%(py2)s = %(py0)s.logger
}.name
} == %(py7)s)r   r   r   py7zassert %(py9)spy9)r   r   namer%   r*   r#   r$   r&   r'   r(   r)   )r+   r   rB   r-   @py_assert6r0   @py_format8@py_format10s           r4    test_logger_is_named_gcloud_authz,TestLogging.test_logger_is_named_gcloud_auth  s    !!7!&&7-7&-7777&-777777{777{777!777&777-7777777r\   NrU   )rX   rY   rZ   r   r   r   r[   r\   r4   r   r      s    $ 8r\   r   c                  $    e Zd ZddZddZddZy)TestTokenCacheStructurec                (   ddl }d}|j                  }||v }|st        j                  d|fd||f      t        j                  |      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      dz  }dd	|iz  }t        t        j                  |            dx}x}}d
}|j                  }||v }|st        j                  d|fd||f      t        j                  |      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      dz  }dd	|iz  }t        t        j                  |            dx}x}}y)u2   _token_cache에 'token'과 'expiry' 키가 있다.r   Nr   )in)z4%(py1)s in %(py5)s
{%(py5)s = %(py3)s._token_cache
}r   )r   r   r<   zassert %(py7)sr   r   )
r   r   r%   r*   r'   r#   r$   r&   r(   r)   )r+   r   @py_assert0r1   r/   rD   r   s          r4   "test_token_cache_has_required_keysz:TestTokenCacheStructure.test_token_cache_has_required_keys,  s    2+222w22222w2222w222222+222+222222222223;333x33333x3333x333333;333;33333333333r\   c                   ddl }|j                  d   }g }d}||u }|}|st        |t              }|}|st	        j
                  d|fd||f      dt        j                         v st	        j                  |      rt	        j                  |      ndt	        j                  |      dz  }dd	|iz  }	|j                  |	       |sd
dt        j                         v st	        j                  t              rt	        j                  t              nddt        j                         v st	        j                  |      rt	        j                  |      nddt        j                         v st	        j                  t              rt	        j                  t              ndt	        j                        dz  }
|j                  |
       t	        j                  |d      i z  }dd|iz  }t        t	        j                  |            dx}x}x}x}}y)uO   모듈 초기 상태에서 _token_cache.token은 None이거나 문자열이다.r   Nr   )is)z%(py2)s is %(py5)scache_token)r   r<   z%(py7)sr   z2%(py13)s
{%(py13)s = %(py9)s(%(py10)s, %(py11)s)
}r   r   )r   r   py11py13   zassert %(py16)spy16)r   r   r   r   r%   r*   r#   r$   r&   r'   append_format_boolopr(   r)   )r+   r   r   rB   r1   r-   r   @py_assert12rD   r   @py_format14@py_format15@py_format17s                r4    test_token_cache_initially_emptyz8TestTokenCacheStructure.test_token_cache_initially_empty3  s	   !..w7BdB{d"Bjc&BB&BBBB{dBBBBBB{BBB{BBBdBBBBBBBBBBjBBBjBBBBBBBBBBBBBBBcBBBcBBB&BBBBBBBBBBBBBBr\   c                z   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(  }|slt        j                  d|fd||f      t        j                  |      t        j                  |      dz  }dd|iz  }t        t        j                  |            dx}x}}|j                  d   }d}||u}|slt        j                  d|fd||f      t        j                  |      t        j                  |      dz  }dd|iz  }t        t        j                  |            dx}x}}ddd       y# 1 sw Y   .xY w# 1 sw Y   3x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   rI   r   r7   )z%(py1)s == %(py4)s)r   r   zassert %(py6)sr   r   )is not)z%(py1)s is not %(py4)s)r   r   r   r   r    r   r!   r"   r   r%   r*   r'   r(   r)   )r+   r   r,   r   r-   r/   r.   r2   s           r4   #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4L8LL48LLLLL48LLLL4LLL8LLLLLLLL++H5ATA5TAAAA5TAAA5AAATAAAAAAA	B 	B3 33 3	B 	Bs<   F1F$F+F$3DF1F!F$$F.	)F11F:NrU   )rX   rY   rZ   r   r   r   r[   r\   r4   r   r   +  s    4CBr\   r   )__doc__
__future__r   builtinsr#   _pytest.assertion.rewrite	assertionrewriter%   sysr>   pathlibr   typingr   unittest.mockr   r   rm   pathinsertr   __file__parentr	   r^   ry   r   r   r[   r\   r4   <module>r      s   
 #   
    *  3tH~,,- .], ],J:. :.D6S 6S|&8 &8\B Br\   