
    ETiІ                     $   d Z ddlZddlmc mZ ddlZddlm	Z	 ddl
mZmZ ddlZddlmZ ej                   j#                  d e e	e      j(                  j(                                ed      5   ed      5  ddlmZ ddd       ddd       d	efd
Zej0                  d	ef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  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( G d* d+      Z) G d, d-      Z* G d. d/      Z+e,d0k(  r ejZ                  ed1g       yy# 1 sw Y   xY w# 1 sw Y   xY w)2u*  
utils/meta_ads_client.py - MetaAdsClient 단위 테스트

테스트 항목:
- 초기화: 환경변수 정상/누락 시 동작
- 토큰 관리: exchange_token, check_token, update_env_token
- 캠페인 CRUD: list, create, get, update, delete
- 광고세트 CRUD: list, create, update, delete
- 크리에이티브: upload_image, create_creative
- 인사이트: get_insights (campaign/adset/ad), 잘못된 타입 ValueError

모든 외부 호출(requests.get, Facebook SDK 메서드)은 Mock으로 대체하여
실제 Meta API를 호출하지 않는다.
    N)Path)	MagicMockpatch)	HTTPError#utils.meta_ads_client.load_env_keys)utils.meta_ads_client.FacebookAdsApi.init)MetaAdsClientreturnc                 H   | j                  dd       | j                  dd       | j                  dd       | j                  dd       t        d	      5  t        d
      5  t               }ddd       ddd       t               _        |S # 1 sw Y   "xY w# 1 sw Y   &xY w)u   테스트용 환경변수를 주입하고, SDK API 연결 없이 MetaAdsClient를 생성한다.

    _account는 MagicMock으로 교체하여 AdAccount SDK 호출을 모두 차단한다.
    META_APP_IDtest_app_idMETA_APP_SECRETtest_app_secretMETA_ACCESS_TOKENtest_access_tokenMETA_AD_ACCOUNT_IDact_123456789r   utils.meta_ads_client.AdAccountN)setenvr   r	   r   _account)monkeypatchcs     1/home/jay/workspace/tests/test_meta_ads_client.py_make_clientr   &   s    
 }m4(*;<*,?@+_=	:	; UCd=e O  AJH   s$   B B+BB	BB!c                     t        |       S )uL   각 테스트에서 재사용할 MetaAdsClient 인스턴스를 제공한다.)r   )r   s    r   clientr   8   s     $$    c                   "    e Zd ZdZd Zd Zd Zy)TestMetaAdsClientInitu.   MetaAdsClient.__init__ 초기화 동작 검증c                    |j                  dd       |j                  dd       |j                  dd       |j                  dd       t        d	      5 }t        d
      5  t               }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                  |      t	        j                  |      dz  }dd|iz  }t        t	        j                  |            dx}x}}|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                  |      dz  }dd|iz  }t        t	        j                  |            dx}x}}|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                  |      dz  }dd|iz  }t        t	        j                  |            dx}x}}|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                  |      dz  }dd|iz  }t        t	        j                  |            dx}x}}j                  ddd       y# 1 sw Y   FxY w# 1 sw Y   KxY w)uq   필수 환경변수가 모두 설정된 경우 예외 없이 초기화되고 속성값이 정확히 설정된다.r   r   r   r   r   r   r   r   r   r   N==)z/%(py2)s
{%(py2)s = %(py0)s._app_id
} == %(py5)sr   py0py2py5assert %(py7)spy7)z3%(py2)s
{%(py2)s = %(py0)s._app_secret
} == %(py5)sz5%(py2)s
{%(py2)s = %(py0)s._access_token
} == %(py5)s)z6%(py2)s
{%(py2)s = %(py0)s._ad_account_id
} == %(py5)s)r   r   r	   _app_id
@pytest_ar_call_reprcompare@py_builtinslocals_should_repr_global_name	_safereprAssertionError_format_explanation_app_secret_access_token_ad_account_idassert_called_once_with)	selfr   	mock_initr   @py_assert1@py_assert4@py_assert3@py_format6@py_format8s	            r   #test_init_success_with_all_env_varsz9TestMetaAdsClientInit.test_init_success_with_all_env_varsF   sb   =-8,.?@.0CD/A>? 	 9eTuNv 	 A	  	  yy)M)yM))))yM))))))q)))q)))y)))M)))))))}}1 11} 11111} 1111111q111q111}111 111111115"55"55555"5555555q555q555555"555555552?2?2222?222222q222q222222?2222222))-9JL_`	  	  	  	 s$   N< N/+N</N9	4N<<Oc                    |j                  dd       |j                  dd       |j                  dd       |j                  dd	       t        d
      5  t        d      5  t        j                  t
        d      5  t                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   yxY w)ub   META_ACCESS_TOKEN이 없으면 ValueError가 발생하고 메시지에 키 이름이 포함된다.r   r   r   r   r   Fraisingr   r   r   r   matchN)r   delenvr   pytestraises
ValueErrorr	   )r7   r   s     r   6test_init_raises_value_error_when_access_token_missingzLTestMetaAdsClientInit.test_init_raises_value_error_when_access_token_missingV   s    =-8,.?@.>/A 89 	 5Al;m 	 z1DE   	  	  	    	  	  	  	 s<   B9!B-=B!B-B9!B*&B--B6	2B99Cc                 (   dD ]  }|j                  |d        t        d      5  t        d      5  t        j                  t              5  t                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   yxY w)uA   여러 필수 키가 모두 없으면 ValueError가 발생한다.)r   r   r   r   Fr@   r   r   N)rD   r   rE   rF   rG   r	   )r7   r   keys      r   7test_init_raises_value_error_when_multiple_keys_missingzMTestMetaAdsClientInit.test_init_raises_value_error_when_multiple_keys_missingb   s    ` 	3CsE2	3 89 	 5Al;m 	 z*   	  	  	    	  	  	  	 s:   BA<A0A<B0A95A<<B	BBN)__name__
__module____qualname____doc__r>   rH   rK    r   r   r   r   C   s    8a 
  r   r   c                       e Zd ZdZd Zd Zy)
TestToDictu'   _to_dict 정적 메서드 동작 검증c                    t               }ddd|j                  _        |j                  |      }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}}|j                  j                          y
)uG   export_all_data()를 가진 객체는 해당 메서드로 변환된다.123testidnamer!   z%(py0)s == %(py3)sresultr$   py3assert %(py5)sr&   N)r   export_all_datareturn_value_to_dictr+   r,   r-   r.   r/   r0   r1   r2   assert_called_once)r7   r   objrZ   @py_assert2r9   @py_format4r<   s           r   0test_to_dict_uses_export_all_data_when_availablez;TestToDict.test_to_dict_uses_export_all_data_when_availablet   s    k27+H(% %v66v66666v6666666v666v66666666666..0r   c                 z   |j                  ddi      }ddi}||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	)
u=   export_all_data()가 없는 객체는 dict()로 변환된다.rJ   valuer!   rY   rZ   r[   r]   r&   N)	r`   r+   r,   r-   r.   r/   r0   r1   r2   )r7   r   rZ   rc   r9   rd   r<   s          r   *test_to_dict_falls_back_to_dict_conversionz5TestToDict.test_to_dict_falls_back_to_dict_conversion~   sv    %!12))v)))))v)))))))v)))v)))))))))))r   N)rL   rM   rN   rO   re   rh   rP   r   r   rR   rR   q   s    11*r   rR   c                   "    e Zd ZdZd Zd Zd Zy)TestExchangeTokenu=   exchange_token: 단기 → 장기 토큰 교환 동작 검증c                 f   t               }ddd|j                  _        t        d|      5 }t        d      5  |j	                         }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}}|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                  |	      dz  }dd|iz  }t        t        j                  |            dx}x}
}	|j                  j                          j                   d   d   }d}||v }|st        j                  d|fd||f      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}}y# 1 sw Y   RxY w# 1 sw Y   WxY w)uc   Graph API가 access_token을 응답하면 새 토큰을 반환하고 내부 상태를 갱신한다.new_long_lived_tokenbearer)access_token
token_type"utils.meta_ads_client.requests.getr_   r   Nr!   rY   rZ   r[   r]   r&   r)   r   r#   r'   r(   r   zoauth/access_tokeninz%(py1)s in %(py3)s
called_urlpy1r\   )r   jsonr_   r   exchange_tokenr+   r,   r-   r.   r/   r0   r1   r2   r4   raise_for_statusra   	call_args)r7   r   	mock_respmock_getrZ   rc   r9   rd   r<   r:   r;   r=   ru   @py_assert0s                 r   0test_exchange_token_returns_new_token_on_successzBTestExchangeToken.test_exchange_token_returns_new_token_on_success   s   K	2"'
	# 6YO	-S[=>	- **,F		- 	- 0/v/////v///////v///v///////////##='==#'=====#'=======v===v===#==='========""557''*1-
#1#z1111#z111#111111z111z1111111	- 	- 	- 	-s"   J&J	J&J#	J&&J0c                    t               }t        d      |j                  _        t	        d|      5  t        j                  t              5  |j                          ddd       ddd       y# 1 sw Y   xY w# 1 sw Y   yxY w)uB   HTTP 오류 발생 시 raise_for_status가 예외를 전파한다.z401 Unauthorizedrp   rq   N)r   r   rz   side_effectr   rE   rF   ry   r7   r   r|   s      r   (test_exchange_token_raises_on_http_errorz:TestExchangeToken.test_exchange_token_raises_on_http_error   so    K	1:;M1N	"".7iP 	(y) (%%'(	( 	(( (	( 	(s#   A:A.A:.A7	3A::Bc                    t               }dddii|j                  _        t        d|      5  t	        j
                  t        d      5  |j                          ddd       ddd       y# 1 sw Y   xY w# 1 sw Y   yxY w)	uH   응답 JSON에 access_token 키가 없으면 ValueError가 발생한다.errormessagezInvalid tokenrp   rq   rn   rB   N)r   rx   r_   r   rE   rF   rG   ry   r   s      r   Gtest_exchange_token_raises_value_error_when_no_access_token_in_responsezYTestExchangeToken.test_exchange_token_raises_value_error_when_no_access_token_in_response   ss    K	'.O0L&M	#7iP 	(z@ (%%'(	( 	(( (	( 	(s#   A7	A+A7+A4	0A77B N)rL   rM   rN   rO   r   r   r   rP   r   r   rj   rj      s    G2(((r   rj   c                       e Zd ZdZd Zd Zy)TestCheckTokenu=   check_token: debug_token 엔드포인트 호출 동작 검증c                 P   t               }ddddgdi|j                  _        t        d|      5 }|j	                         }ddd       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g}||k(  }|slt        j                  d|fd||f      t        j                  |      t        j                  |      dz  }dd|iz  }	t        t        j                  |	            dx}x}}|j                  j                          j                  d   d   }
d}||
v }|st        j                  d|fd||
f      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}}y# 1 sw Y   xY w)u2   debug_token 응답의 data 필드를 반환한다.dataTl   c(	 ads_management)is_valid
expires_atscopesrp   rq   Nr   isz%(py1)s is %(py4)srw   py4assert %(py6)spy6r   r!   z%(py1)s == %(py4)sr   debug_tokenrr   rt   ru   rv   r]   r&   )r   rx   r_   r   check_tokenr+   r,   r0   r1   r2   rz   ra   r{   r-   r.   r/   )r7   r   r|   r}   rZ   r~   r;   rc   @py_format5@py_format7ru   rd   r<   s                r   #test_check_token_returns_data_fieldz2TestCheckToken.test_check_token_returns_data_field   s   K	 (+,'
	# 7iP 	*T\'')F	* j!)T)!T))))!T)))!)))T)))))))h5$4#55#55555#5555555#55555555""557''*1-
*}
****}
***}******
***
*******	* 	*s   HH%c                    t               }ddi|j                  _        t        d|      5  |j	                         }ddd       ddi}|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)u>   응답에 data 키가 없으면 응답 전체를 반환한다.r   Frp   rq   Nr!   rY   rZ   r[   r]   r&   )r   rx   r_   r   r   r+   r,   r-   r.   r/   r0   r1   r2   )r7   r   r|   rZ   rc   r9   rd   r<   s           r   6test_check_token_returns_raw_response_when_no_data_keyzETestCheckToken.test_check_token_returns_raw_response_when_no_data_key   s    K	'15&9	#7iP 	*'')F	* %e,,v,,,,,v,,,,,,,v,,,v,,,,,,,,,,,	* 	*s   C--C6N)rL   rM   rN   rO   r   r   rP   r   r   r   r      s    G+(-r   r   c                   (    e Zd ZdZd Zd Zd Zd Zy)TestUpdateEnvTokenuN   update_env_token: .env.keys 파일의 토큰 라인 업데이트 동작 검증c                     |dz  }|j                  d       t        |      |_        |j                  d       |j	                         }d}||v }|st        j                  d|fd||f      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}}d}||v}|st        j                  d|fd||f      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}}d}||v }|st        j                  d|fd||f      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}}|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                  |
      dz  }dd|iz  }t        t        j                  |            d
x}	x}}
y
)uQ   'export META_ACCESS_TOKEN=...' 형식의 라인을 새 토큰으로 교체한다.	.env.keysz[export META_APP_ID=app_id
export META_ACCESS_TOKEN=old_token
export META_APP_SECRET=secret
brand_new_tokenrr   rt   contentrv   r]   r&   N	old_tokennot inz%(py1)s not in %(py3)szMETA_APP_ID=app_idr!   r)   r   r#   r'   r(   )
write_textstr_env_keys_pathupdate_env_token	read_textr+   r,   r0   r-   r.   r/   r1   r2   r4   )r7   r   tmp_pathenv_filer   r~   rc   rd   r<   r9   r:   r;   r=   s                r   1test_update_env_token_replaces_export_prefix_linezDTestUpdateEnvToken.test_update_env_token_replaces_export_prefix_line   s   k)l	
 !$H 12$$& + G++++ G+++ ++++++G+++G+++++++){')))){'))){))))))')))')))))))#.#w....#w...#......w...w.......##8'88#'88888#'8888888v888v888#888'88888888r   c                 &   |dz  }|j                  d       t        |      |_        |j                  d       |j	                         }d}||v }|st        j                  d|fd||f      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}}d}||v}|st        j                  d|fd||f      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}}y
)uI   'META_ACCESS_TOKEN=...' (export 없음) 형식의 라인도 교체한다.r   z"META_ACCESS_TOKEN=old_plain_token
new_plain_tokenrr   rt   r   rv   r]   r&   Nold_plain_tokenr   r   r   r   r   r   r   r+   r,   r0   r-   r.   r/   r1   r2   	r7   r   r   r   r   r~   rc   rd   r<   s	            r   0test_update_env_token_replaces_plain_prefix_linezCTestUpdateEnvToken.test_update_env_token_replaces_plain_prefix_line   s    k)AB #H 12$$& + G++++ G+++ ++++++G+++G+++++++ / //// /// ////////////////r   c                    |dz  }|j                  d       t        |      |_        |j                  d       |j	                         }d}||v }|st        j                  d|fd||f      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}}y
)uK   파일에 META_ACCESS_TOKEN 라인이 없으면 파일 끝에 추가한다.r   zexport META_APP_ID=app_id
appended_tokenrr   rt   r   rv   r]   r&   Nr   r   s	            r   1test_update_env_token_appends_when_line_not_foundzDTestUpdateEnvToken.test_update_env_token_appends_when_line_not_found   s    k)9: #H 01$$&*7****7*********7***7*******r   c                 L    t        |dz        |_        |j                  d       y)uC   파일이 존재하지 않으면 예외 없이 조기 반환한다.znonexistent.env.keys
some_tokenN)r   r   r   )r7   r   r   s      r   7test_update_env_token_does_nothing_when_file_not_existszJTestUpdateEnvToken.test_update_env_token_does_nothing_when_file_not_exists   s%     #H/E$E F 	-r   N)rL   rM   rN   rO   r   r   r   r   rP   r   r   r   r      s    X9 
0	+.r   r   c                       e Zd ZdZd Zy)TestGetAccountInfouA   get_account_info: AdAccount.api_get 호출 및 dict 변환 검증c                    t               }ddddd|j                  _        ||j                  j                  _        |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}}|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                  j                  j                          y)u4   api_get 결과를 dict로 변환하여 반환한다.r   zTest Account   KRW)rW   rX   account_statuscurrencyrX   r!   r   r   r   r   Nr   )r   r^   r_   r   api_getget_account_infor+   r,   r0   r1   r2   ra   )	r7   r   	mock_datarZ   r~   r;   rc   r   r   s	            r   "test_get_account_info_returns_dictz5TestGetAccountInfo.test_get_account_info_returns_dict  s    K	!"	2
	!!. 09,((*f~//~////~///~//////////j!*U*!U****!U***!***U*******224r   N)rL   rM   rN   rO   r   rP   r   r   r   r     s
    K5r   r   c                   "    e Zd ZdZd Zd Zd Zy)TestListCampaignsuO   list_campaigns: AdAccount.get_campaigns mock을 통한 list[dict] 반환 검증c                    t               t               }}ddd|j                  _        ddd|j                  _        ||g|j                  j                  _        |j                         }t        |      }d}||k(  }|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}}|d   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}}|d   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}}y)uD   get_campaigns 결과를 dict 목록으로 변환하여 반환한다.camp_1z
Campaign ArV   camp_2z
Campaign B   r!   z0%(py3)s
{%(py3)s = %(py0)s(%(py1)s)
} == %(py6)slenrZ   r$   rw   r\   r   assert %(py8)spy8Nr   rX   r   r   r   r   r   rW   )r   r^   r_   r   get_campaignslist_campaignsr   r+   r,   r-   r.   r/   r0   r1   r2   )r7   r   camp1camp2rZ   rc   @py_assert5r:   r   @py_format9r~   r;   r   s                r   )test_list_campaigns_returns_list_of_dictsz;TestListCampaigns.test_list_campaigns_returns_list_of_dicts*  s    {IKu4<l-S*4<l-S*6;U^%%2&&(6{a{a{ass66{aay 0L0 L0000 L000 000L0000000ay*(*(****(******(*******r   c                    g |j                   j                  _        |j                  d       |j                   j                  j                  \  }}|d   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}}y
)u;   limit 인자가 params에 포함되어 SDK에 전달된다.
   limitparamsr   r!   r   r   r   r   N)
r   r   r_   r   r{   r+   r,   r0   r1   r2   	r7   r   _kwargsr~   r;   rc   r   r   s	            r   &test_list_campaigns_passes_limit_paramz8TestListCampaigns.test_list_campaigns_passes_limit_param7  s    57%%2B'OO11;;	6h(.B.(B....(B...(...B.......r   c                    g |j                   j                  _        ddg}|j                  |       |j                   j                  j                  \  }}|d   }||k(  }|st        j                  d|fd||f      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}}y)J   fields 인자를 지정하면 해당 필드 목록이 SDK에 전달된다.rW   rX   fieldsr   r!   z%(py1)s == %(py3)scustom_fieldsrv   r]   r&   N)r   r   r_   r   r{   r+   r,   r0   r-   r.   r/   r1   r2   )	r7   r   r   r   r   r~   rc   rd   r<   s	            r   &test_list_campaigns_uses_custom_fieldsz8TestListCampaigns.test_list_campaigns_uses_custom_fields@  s    57%%2v]3OO11;;	6h0=0000=000000000=000=0000000r   N)rL   rM   rN   rO   r   r   r   rP   r   r   r   r   '  s    Y+/1r   r   c                   (    e Zd ZdZd Zd Zd Zd Zy)TestCreateCampaignuN   create_campaign: AdAccount.create_campaign mock을 통한 생성 동작 검증c                    t               }dddd|j                  _        ||j                  j                  _        |j	                  ddd      }|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}}|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}}y)u<   create_campaign 결과를 dict로 변환하여 반환한다.camp_newzNew CampaignPAUSEDrW   rX   statusOUTCOME_TRAFFIC)rX   	objectiver   rW   r!   r   r   r   r   Nr   )
r   r^   r_   r   create_campaignr+   r,   r0   r1   r2   )	r7   r   mock_campaignrZ   r~   r;   rc   r   r   s	            r   !test_create_campaign_returns_dictz4TestCreateCampaign.test_create_campaign_returns_dictN  s    !"6
%%2
 8E''4''' ( 
 d|)z)|z))))|z)))|)))z)))))))h+8+8++++8++++++8+++++++r   c                    t               }ddi|j                  _        ||j                  j                  _        |j	                  ddd       |j                  j                  j
                  \  }}|d   }|j                  }d} ||      }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                  |      t        j                  |	      dz  }dd|iz  }t        t        j                  |            dx}x}x}x}
}	y)u4   daily_budget이 지정되면 params에 포함된다.rW   camp_budgetzBudget CampaignOUTCOME_AWARENESSi'  )rX   r   daily_budgetr   r   r!   )zI%(py6)s
{%(py6)s = %(py2)s
{%(py2)s = %(py0)s.get
}(%(py4)s)
} == %(py9)s)r$   r%   r   r   py9assert %(py11)spy11N)r   r^   r_   r   r   r{   getr+   r,   r-   r.   r/   r0   r1   r2   )r7   r   r   r   r   r   r9   r;   r   @py_assert8@py_assert7@py_format10@py_format12s                r   5test_create_campaign_includes_daily_budget_when_givenzHTestCreateCampaign.test_create_campaign_includes_daily_budget_when_givena  s   !6:M5J%%27D''4") 	 	
 OO33==	6!zz2.2z.)2U2)U2222)U222222v222v222z222.222)222U22222222r   c                 4   t               }ddi|j                  _        ||j                  j                  _        |j	                  dd       |j                  j                  j
                  \  }}|d   }d}||v}|st        j                  d|fd	||f      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}}y)u=   daily_budget=None이면 params에 daily_budget 키가 없다.rW   camp_no_budgetz	No Budgetr   rX   r   r   r   r   r   rv   r]   r&   N)r   r^   r_   r   r   r{   r+   r,   r0   r-   r.   r/   r1   r2   )
r7   r   r   r   r   r   r~   rc   rd   r<   s
             r   1test_create_campaign_omits_daily_budget_when_nonezDTestCreateCampaign.test_create_campaign_omits_daily_budget_when_noneq  s    !6:<L5M%%27D''4K;LMOO33==	6!+~V++++~V+++~++++++V+++V+++++++r   c                    t               }ddi|j                  _        ||j                  j                  _        |j	                  dd       |j                  j                  j
                  \  }}|d   d   }g }||k(  }|slt        j                  d|fd	||f      t        j                  |      t        j                  |      d
z  }dd|iz  }	t        t        j                  |	            dx}x}}y)uN   special_ad_categories를 지정하지 않으면 빈 리스트가 전달된다.rW   camp_catzCat Campaignr   r  r   special_ad_categoriesr!   r   r   r   r   N)r   r^   r_   r   r   r{   r+   r,   r0   r1   r2   )
r7   r   r   r   r   r~   r;   rc   r   r   s
             r   8test_create_campaign_default_special_ad_categories_emptyzKTestCreateCampaign.test_create_campaign_default_special_ad_categories_empty}  s    !6:J5G%%27D''4N>OPOO33==	6h 78>B>8B>>>>8B>>>8>>>B>>>>>>>r   N)rL   rM   rN   rO   r   r  r  r
  rP   r   r   r   r   K  s    X,&3 
,	?r   r   c                       e Zd ZdZd Zy)TestGetCampaignuB   get_campaign: Campaign.api_get mock을 통한 단일 조회 검증c                    t               }dddd|j                  _        t        d      5 }||j                  j                  _        |j                  d      }ddd       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}}|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       y# 1 sw Y   xY w)u6   Campaign(id).api_get() 결과를 dict로 반환한다.camp_123zTarget CampaignACTIVEr   utils.meta_ads_client.CampaignNrW   r!   r   r   r   r   r   )r   r^   r_   r   r   get_campaignr+   r,   r0   r1   r2   r6   )
r7   r   mock_resultMockCampaignrZ   r~   r;   rc   r   r   s
             r   test_get_campaign_returns_dictz.TestGetCampaign.test_get_campaign_returns_dict  s   k%4
##0 34 	5=HL%%--:((4F	5 d|)z)|z))))|z)))|)))z)))))))h+8+8++++8++++++8+++++++,,Z8	5 	5s   -E22E<N)rL   rM   rN   rO   r  rP   r   r   r  r    s
    L9r   r  c                       e Zd ZdZd Zd Zy)TestUpdateCampaignuG   update_campaign: Campaign.api_update mock을 통한 업데이트 검증c                 (   t               }ddi|j                  _        t        d      5 }||j                  j                  _        |j                  ddd      }ddd       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}}j                  j                  j                  \  }
}|d   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}}y# 1 sw Y   0xY w)*   api_update 결과를 dict로 반환한다.successTr  r  r  Updated)r   rX   Nr   r   r   r   r   r   r   r!   r   )r   r^   r_   r   
api_updateupdate_campaignr+   r,   r0   r1   r2   r{   )r7   r   r  r  rZ   r~   r;   rc   r   r   r   r   s               r   !test_update_campaign_returns_dictz4TestUpdateCampaign.test_update_campaign_returns_dict  s1   k4=t3D##034 	Y@KL%%00=++Jxi+XF	Y i (D( D(((( D((( (((D((((((( --88BB	6h)5X5)X5555)X555)555X5555555	Y 	Ys   0FFc                 "   t               }i |j                  _        t        d      5 }||j                  j                  _        |j                  dd       ddd       j                  j                  j                  \  }}|d   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}}y# 1 sw Y   xY w)uO   **params 키워드 인자가 api_update의 params 딕셔너리로 전달된다.r  camp_456  )r   Nr   r   r!   r   r   r   r   )r   r^   r_   r   r  r  r{   r+   r,   r0   r1   r2   )r7   r   r  r  r   r   r~   r;   rc   r   r   s              r   ,test_update_campaign_passes_kwargs_as_paramsz?TestUpdateCampaign.test_update_campaign_passes_kwargs_as_params  s    k35##034 	B@KL%%00="":D"A	B !--88BB	6h/747/47777/4777/77747777777	B 	Bs   /DDN)rL   rM   rN   rO   r  r!  rP   r   r   r  r    s    Q6
8r   r  c                       e Zd ZdZd Zd Zy)TestDeleteCampaignuA   delete_campaign: Campaign.api_delete mock을 통한 삭제 검증c                 P   t        d      5 }d|j                  j                  _        |j                  d      }ddd       d}|u }|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}}j                  d       |j                  j                  j                          y# 1 sw Y   xY w)+   api_delete 성공 시 True를 반환한다.r  Ncamp_delTr   z%(py0)s is %(py3)srZ   r[   r]   r&   )r   r_   
api_deletedelete_campaignr+   r,   r-   r.   r/   r0   r1   r2   r6   ra   )r7   r   r  rZ   rc   r9   rd   r<   s           r   ,test_delete_campaign_returns_true_on_successz?TestDeleteCampaign.test_delete_campaign_returns_true_on_success  s    34 	8@DL%%00=++J7F	8 v~vvv,,Z8!!,,??A	8 	8s   -DD%c           
      &   ddl m}  |dddddi dd	d
di      }t        d      5 }||j                  j                  _        t        j                  |      5  |j                  d       ddd       ddd       y# 1 sw Y   xY w# 1 sw Y   yxY w)uF   SDK가 FacebookRequestError를 발생시키면 그대로 전파된다.r   )FacebookRequestErrorz	API errorDELETEz/camp_error)methodpathi  r   zCampaign not foundd   )r   code)r   request_contexthttp_statushttp_headersbodyr  
camp_errorN)	facebook_business.exceptionsr,  r   r_   r(  r   rE   rF   r)  )r7   r   r,  excr  s        r   -test_delete_campaign_propagates_sdk_exceptionz@TestDeleteCampaign.test_delete_campaign_propagates_sdk_exception  s    E"'/G';SIJ
 34 	5?BL%%00<34 5&&|45	5 	55 5	5 	5s#   1BA;*B;B	 BBN)rL   rM   rN   rO   r*  r9  rP   r   r   r#  r#    s    KB5r   r#  c                   "    e Zd ZdZd Zd Zd Zy)TestListAdsetsuM   list_adsets: 계정 전체 및 캠페인별 광고세트 목록 조회 검증c                    t               }ddd|j                  _        |g|j                  j                  _        |j                         }t        |      }d}||k(  }|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}}|d   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                  j                  j                          y)uD   campaign_id가 없으면 계정 전체 광고세트를 조회한다.adset_1zAdSet ArV   r   r!   r   r   rZ   r   r   r   Nr   rX   r   r   r   r   )r   r^   r_   r   get_ad_setslist_adsetsr   r+   r,   r-   r.   r/   r0   r1   r2   ra   )r7   r   adsetrZ   rc   r   r:   r   r   r~   r;   r   s               r   1test_list_adsets_from_account_when_no_campaign_idz@TestListAdsets.test_list_adsets_from_account_when_no_campaign_id  s6   4=y-Q*497##0##%6{a{a{ass66{aay -I- I---- I--- ---I-------##668r   c                 L   t               }ddd|j                  _        t        d      5 }|g|j                  j                  _        |j                  d      }ddd       d   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       |j                  j                  j                          y# 1 sw Y   xY w)uM   campaign_id가 지정되면 해당 캠페인의 광고세트를 조회한다.
adset_campcamp_xyz)rW   campaign_idr  )rE  Nr   rE  r!   r   r   r   r   )r   r^   r_   r   r>  r?  r+   r,   r0   r1   r2   r6   ra   )
r7   r   r@  r  rZ   r~   r;   rc   r   r   s
             r   5test_list_adsets_from_campaign_when_campaign_id_givenzDTestListAdsets.test_list_adsets_from_campaign_when_campaign_id_given  s    4@Q[-\*34 	@BGL%%11>''J'?F	@ ay'5:5':5555':555'555:5555555,,Z8!!--@@B	@ 	@s   /DD#c                    g |j                   j                  _        |j                  d       |j                   j                  j                  \  }}|d   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}}y
)u2   limit 인자가 SDK 호출 params에 포함된다.   r   r   r   r!   r   r   r   r   N)
r   r>  r_   r?  r{   r+   r,   r0   r1   r2   r   s	            r   test_list_adsets_passes_limitz,TestListAdsets.test_list_adsets_passes_limit  s    35##0#OO//99	6h(-A-(A----(A---(---A-------r   N)rL   rM   rN   rO   rA  rF  rI  rP   r   r   r;  r;    s    W
9C.r   r;  c                       e Zd ZdZd Zd Zy)TestCreateAdsetuB   create_adset: AdAccount.create_ad_set mock을 통한 생성 검증c                    t               }dddd|j                  _        ||j                  j                  _        dddgii}|j                  ddd	|d
d      }|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}}|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}}y)u:   create_ad_set 결과를 dict로 변환하여 반환한다.	adset_newz	New AdSetr   r   geo_locations	countriesKRcamp_abcr   REACHIMPRESSIONS)rE  rX   r   	targetingoptimization_goalbilling_eventrW   r!   r   r   r   r   Nr   )r   r^   r_   r   create_ad_setcreate_adsetr+   r,   r0   r1   r2   )
r7   r   
mock_adsetrT  rZ   r~   r;   rc   r   r   s
             r   test_create_adset_returns_dictz.TestCreateAdset.test_create_adset_returns_dict	  s   [
3

""/
 6@%%2${TF&;<	$$"%' % 
 d|*{*|{****|{***|***{*******h+8+8++++8++++++8+++++++r   c           	      P   t               }ddi|j                  _        ||j                  j                  _        ddd}|j                  ddd|d	d	d
       |j                  j                  j                  \  }}|d   }|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}	}|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}	}|d   }||k(  }	|	st        j                  d|	fd||f      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}}	|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}	}y)uM   모든 파라미터가 SDK create_ad_set 호출에 올바르게 전달된다.rW   adset_p   A   )age_minage_maxcamp_pzParam AdSeti  LINK_CLICKSr  )rE  rX   r   rT  rU  rV  r   r   rE  r!   r   r   r   r   Nr   rT  r   rv   r]   r&   r   )r   r^   r_   r   rW  rX  r{   r+   r,   r0   r1   r2   r-   r.   r/   )r7   r   rY  rT  r   r   r   r~   r;   rc   r   r   rd   r<   s                 r   'test_create_adset_passes_correct_paramsz7TestCreateAdset.test_create_adset_passes_correct_params   s   [
372C
""/5?%%2 "r2	 +' 	 	
 OO11;;	6!m$00$0000$000$0000000000n%--%----%---%----------k"/"i////"i///"//////i///i///////h+8+8++++8++++++8+++++++r   N)rL   rM   rN   rO   rZ  rc  rP   r   r   rK  rK    s    L,.,r   rK  c                       e Zd ZdZd Zy)TestUpdateAdsetuA   update_adset: AdSet.api_update mock을 통한 업데이트 검증c                 &   t               }ddi|j                  _        t        d      5 }||j                  j                  _        |j                  dd      }ddd       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}}j                  j                  j                  \  }
}|d   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}}y# 1 sw Y   0xY w)r  updatedTutils.meta_ads_client.AdSet	adset_123r  )r   Nr   r   r   r   r   r   r   r!   r   )r   r^   r_   r   r  update_adsetr+   r,   r0   r1   r2   r{   )r7   r   r  	MockAdSetrZ   r~   r;   rc   r   r   r   r   s               r   test_update_adset_returns_dictz.TestUpdateAdset.test_update_adset_returns_dict<  s/   k4=t3D##001 	GY=HI""--:((X(FF	G i (D( D(((( D((( (((D(((((((**55??	6h)5X5)X5555)X555)555X5555555	G 	Gs   /FFN)rL   rM   rN   rO   rl  rP   r   r   re  re  9  s
    K6r   re  c                       e Zd ZdZd Zy)TestDeleteAdsetu;   delete_adset: AdSet.api_delete mock을 통한 삭제 검증c                    t        d      5 }d|j                  j                  _        |j                  d      }ddd       d}|u }|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}}j                  d       y# 1 sw Y   xY w)r%  rh  N	adset_delTr   r'  rZ   r[   r]   r&   )r   r_   r(  delete_adsetr+   r,   r-   r.   r/   r0   r1   r2   r6   )r7   r   rk  rZ   rc   r9   rd   r<   s           r   test_delete_adset_returns_truez.TestDeleteAdset.test_delete_adset_returns_trueM  s    01 	6Y=AI""--:((5F	6 v~vvv))+6	6 	6s   -C88DN)rL   rM   rN   rO   rr  rP   r   r   rn  rn  J  s
    E7r   rn  c                   "    e Zd ZdZd Zd Zd Zy)TestUploadImageu3   upload_image: AdImage 생성 및 hash 반환 검증c                 h   |dz  }|j                  d       t               }ddi|j                  _        ||j                  j
                  _        |j                  t        |            }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}}|j                  j
                  j!                          y)uL   hash 키가 최상위에 있는 응답에서 hash 문자열을 반환한다.ztest.pngs   PNG_CONTENThash
abc123hashr!   rY   rZ   r[   r]   r&   N)write_bytesr   r^   r_   r   create_ad_imageupload_imager   r+   r,   r-   r.   r/   r0   r1   r2   ra   
r7   r   r   img_file
mock_imagerZ   rc   r9   rd   r<   s
             r   1test_upload_image_returns_hash_from_flat_responsezATestUploadImage.test_upload_image_returns_hash_from_flat_response_  s    j(^,[
39<2H
""/7A''4$$S]3%%v%%%%v%%%%%%v%%%v%%%%%%%%%%''::<r   c                 (   |dz  }|j                  d       t               }ddddiii|j                  _        ||j                  j
                  _        |j                  t        |            }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)uK   images 키 아래에 중첩된 응답 구조에서도 hash를 추출한다.z
nested.jpgs   JPG_CONTENTimagesrv  nested_hash_xyzr!   rY   rZ   r[   r]   r&   N)rx  r   r^   r_   r   ry  rz  r   r+   r,   r-   r.   r/   r0   r1   r2   r{  s
             r   :test_upload_image_returns_hash_from_nested_images_responsezJTestUploadImage.test_upload_image_returns_hash_from_nested_images_responsem  s    l*^,[
3;lVUfLg=h2i
""/7A''4$$S]3**v*****v*******v***v***********r   c                     t        |dz        }t        j                  t        d      5  |j	                  |       ddd       y# 1 sw Y   yxY w)uT   존재하지 않는 파일 경로를 전달하면 FileNotFoundError가 발생한다.zno_image.pngrB   N)r   rE   rF   FileNotFoundErrorrz  )r7   r   r   nonexistents       r   'test_upload_image_raises_file_not_foundz7TestUploadImage.test_upload_image_raises_file_not_foundz  sC    (^34]],NC 	-,	- 	- 	-s   AAN)rL   rM   rN   rO   r~  r  r  rP   r   r   rt  rt  \  s    ==+-r   rt  c                   (    e Zd ZdZd Zd Zd Zd Zy)TestCreateCreativeuJ   create_creative: AdAccount.create_ad_creative mock을 통한 생성 검증c                    t               }ddd|j                  _        ||j                  j                  _        |j                  ddddd      }|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}}y)u?   create_ad_creative 결과를 dict로 변환하여 반환한다.
creative_1zTest CreativerV   abc123page_456zBuy now!zhttps://example.comrX   
image_hashpage_idr   linkrW   r!   r   r   r   r   N)r   r^   r_   r   create_ad_creativecreate_creativer+   r,   r0   r1   r2   )	r7   r   mock_creativerZ   r~   r;   rc   r   r   s	            r   !test_create_creative_returns_dictz4TestCreateCreative.test_create_creative_returns_dict  s    !#6
%%2 ;H**7'' & ( 
 d|+|+||++++||+++|+++|+++++++r   c                     t               }ddi|j                  _        ||j                  j                  _        |j                  ddddd       |j                  j                  j                  \  }}|d	   }|d
   d   }|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}	}y)u/   link가 지정되면 link_data에 포함된다.rW   c_linkzLink Creative	hash_link	page_linkzClick mezhttps://landing.example.comr  r   object_story_spec	link_datar  r!   r   r   r   r   Nr   r^   r_   r   r  r  r{   r+   r,   r0   r1   r2   )r7   r   r  r   r   r   r  r~   r;   rc   r   r   s               r   -test_create_creative_includes_link_when_givenz@TestCreateCreative.test_create_creative_includes_link_when_given  s    !6:H5E%%2:G**7 ". 	 	
 OO66@@	6!./<	 A$AA $AAAAA $AAAA AAA$AAAAAAAAr   c                 H   t               }ddi|j                  _        ||j                  j                  _        |j                  dddd       |j                  j                  j                  \  }}|d   }|d	   d
   }d}||v}|st        j                  d|fd||f      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}}y)u0   link=None이면 link_data에 link 키가 없다.rW   	c_no_linkzNo Linkhash_nolinkpage_nolinkz
Image onlyrX   r  r  r   r   r  r  r  r   r   rv   r]   r&   N)r   r^   r_   r   r  r  r{   r+   r,   r0   r-   r.   r/   r1   r2   )r7   r   r  r   r   r   r  r~   rc   rd   r<   s              r   )test_create_creative_omits_link_when_nonez<TestCreateCreative.test_create_creative_omits_link_when_none  s    !6:K5H%%2:G**7$! 	 	 	
 OO66@@	6!./<	&vY&&&&vY&&&v&&&&&&Y&&&Y&&&&&&&r   c                     t               }ddi|j                  _        ||j                  j                  _        |j                  dddd       |j                  j                  j                  \  }}|d   d	   }|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}}|d   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}}|d   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}}y)uE   object_story_spec에 page_id와 message가 올바르게 설정된다.rW   c_pagezPage Creative	hash_pagemy_page_789zHello Worldr  r   r  r  r!   r   r   r   r   Nr  r   r  r  )r7   r   r  r   r   specr~   r;   rc   r   r   s              r   -test_create_creative_sets_page_id_and_messagez@TestCreateCreative.test_create_creative_sets_page_id_and_message  st   !6:H5E%%2:G**7 "!!	 	 	
 OO66@@	6h 34I/-/-////-//////-///////K +<}<+}<<<<+}<<<+<<<}<<<<<<<K .=+=.+====.+===.===+=======r   N)rL   rM   rN   rO   r  r  r  r  rP   r   r   r  r    s    T,&B&'$>r   r  c                   V    e Zd ZdZdedefdZd Zd Zd Z	d Z
d	 Zd
 Zd Zd Zd Zy)TestGetInsightsu6   get_insights: 광고 성과 인사이트 조회 검증r   r
   c                 <    t               }||j                  _        |S )uC   export_all_data를 가진 인사이트 mock 객체를 생성한다.)r   r^   r_   )r7   r   ms      r   _make_insightzTestGetInsights._make_insight  s    K)-&r   c                    | j                  ddd      }t        d      5 }|g|j                  j                  _        |j                  dd      }ddd       t	              }d	}||k(  }|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}}|d   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       y# 1 sw Y   xY w)uN   object_type='campaign'일 때 Campaign 객체의 get_insights를 호출한다.1000z50.00)impressionsspendr  camp_insightcampaignobject_typeNr   r!   r   r   rZ   r   r   r   r   r  r   r   r   r   r  r   r_   get_insightsr   r+   r,   r-   r.   r/   r0   r1   r2   r6   )r7   r   insightr  rZ   rc   r   r:   r   r   r~   r;   r   s                r   #test_get_insights_for_campaign_typez3TestGetInsights.test_get_insights_for_campaign_type  sX   $$Vg%NO34 	QCJ)L%%22?((Z(PF	Q 6{a{a{ass66{aay'161'61111'6111'11161111111,,^<	Q 	Qs   0G==Hc                    | j                  ddd      }t        d      5 }|g|j                  j                  _        |j                  dd      }ddd       d	   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       y# 1 sw Y   xY w)uH   object_type='adset'일 때 AdSet 객체의 get_insights를 호출한다.200z2.00)clicksctrrh  adset_insightr@  r  Nr   r  r!   r   r   r   r   
r  r   r_   r  r+   r,   r0   r1   r2   r6   )
r7   r   r  rk  rZ   r~   r;   rc   r   r   s
             r    test_get_insights_for_adset_typez0TestGetInsights.test_get_insights_for_adset_type  s    $$f%EF01 	OY@GyI""//<((g(NF	O ay"+e+"e++++"e+++"+++e+++++++))/:	O 	Os   0C--C6c                    | j                  ddi      }t        d      5 }|g|j                  j                  _        |j                  dd      }ddd       d   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       y# 1 sw Y   xY w)uB   object_type='ad'일 때 Ad 객체의 get_insights를 호출한다.cpmz3.50zutils.meta_ads_client.Ad
ad_insightadr  Nr   r!   r   r   r   r   r  )
r7   r   r  MockAdrZ   r~   r;   rc   r   r   s
             r   test_get_insights_for_ad_typez-TestGetInsights.test_get_insights_for_ad_type  s    $$eV_5-. 	I&=DIF,,9((4(HF	I ay)6)6))))6))))))6)))))))&&|4	I 	Is   0C,,C5c                     t        j                  t        d      5  |j                  dd       ddd       y# 1 sw Y   yxY w)uF   지원하지 않는 object_type 전달 시 ValueError가 발생한다.u   지원하지 않는 object_typerB   some_idinvalid_typer  N)rE   rF   rG   r  )r7   r   s     r   8test_get_insights_invalid_object_type_raises_value_errorzHTestGetInsights.test_get_insights_invalid_object_type_raises_value_error  s=    ]]:-NO 	G	~F	G 	G 	Gs	   9Ac                    | j                  ddi      }t        d      5 }|g|j                  j                  _        |j                  d       ddd       j                  j                  j                  \  }}|d   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}}y# 1 sw Y   xY w)uY   date_preset과 time_range 미지정 시 'last_7d' 프리셋이 기본으로 사용된다.r  z10.00r  camp_defaultNr   date_presetlast_7dr!   r   r   r   r   
r  r   r_   r  r{   r+   r,   r0   r1   r2   r7   r   r  r  r   r   r~   r;   rc   r   r   s              r   8test_get_insights_uses_default_date_preset_when_no_rangezHTestGetInsights.test_get_insights_uses_default_date_preset_when_no_range  s    $$gw%7834 	0CJ)L%%22?/	0 !--::DD	6h.;);.);;;;.);;;.;;;);;;;;;;	0 	0s   .C<<Dc                    | j                  i       }t        d      5 }|g|j                  j                  _        |j                  dd       ddd       j                  j                  j                  \  }}|d   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}}y# 1 sw Y   xY w)uA   date_preset을 지정하면 해당 값이 params에 포함된다.r  camp_presetlast_30d)r  Nr   r  r!   r   r   r   r   r  r  s              r   (test_get_insights_uses_given_date_presetz8TestGetInsights.test_get_insights_uses_given_date_preset  s    $$R(34 	GCJ)L%%22?:F	G !--::DD	6h.<*<.*<<<<.*<<<.<<<*<<<<<<<	G 	Gs   0C<<Dc                    | j                  i       }ddd}t        d      5 }|g|j                  j                  _        |j                  d|       ddd       j                  j                  j                  \  }}|d   }|j
                  }d	}	 ||	      }
|
|k(  }|st        j                  d
|fd|
|f      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}
}d}|d   }||v}|slt        j                  d|fd||f      t        j                  |      t        j                  |      dz  }dd|iz  }t        t        j                  |            dx}x}}y# 1 sw Y   xY w)uS   time_range를 지정하면 date_preset 없이 time_range가 params에 포함된다.z
2024-01-01z
2024-01-31)sinceuntilr  
camp_range)
time_rangeNr   r  r!   )zI%(py7)s
{%(py7)s = %(py3)s
{%(py3)s = %(py1)s.get
}(%(py5)s)
} == %(py9)s)rw   r\   r&   r(   r   r   r   r  r   )z%(py1)s not in %(py4)sr   r   r   )r  r   r_   r  r{   r   r+   r,   r0   r-   r.   r/   r1   r2   )r7   r   r  r  r  r   r   r~   rc   r:   @py_assert6r   r   r  r;   r   r   s                    r   'test_get_insights_uses_given_time_rangez7TestGetInsights.test_get_insights_uses_given_time_range  s{   $$R(+lC
34 	ECJ)L%%22?D	E !--::DD	6h?##?L?#L1?1Z????1Z??????#???L???1??????Z???Z????????4F8$44}$44444}$4444}444$44444444	E 	Es   0G>>Hc                 h   | j                  ddi      }ddg}t        d      5 }|g|j                  j                  _        |j                  d|       ddd       j                  j                  j                  \  }}|d   }||k(  }|st        j                  d	|fd
||f      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}}y# 1 sw Y   xY w)r   r  500r  r  camp_fieldsr   Nr   r!   r   r   rv   r]   r&   )r  r   r_   r  r{   r+   r,   r0   r-   r.   r/   r1   r2   )r7   r   r  r   r  r   r   r~   rc   rd   r<   s              r   $test_get_insights_uses_custom_fieldsz4TestGetInsights.test_get_insights_uses_custom_fields)  s    $$mU%;<&034 	ECJ)L%%22?mD	E !--::DD	6h0=0000=000000000=000=0000000	E 	Es   0D((D1c                    | j                  ddi      }t        d      5 }|g|j                  j                  _        |j                  dd      }ddd       t	              }d}||k(  }|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}}j                  d       y# 1 sw Y   ,xY w)uY   object_type은 대소문자를 구분하지 않는다 (Campaign, CAMPAIGN 모두 허용).r  100r  	camp_caseCAMPAIGNr  Nr   r!   r   r   rZ   r   r   r   r  )
r7   r   r  r  rZ   rc   r   r:   r   r   s
             r   .test_get_insights_object_type_case_insensitivez>TestGetInsights.test_get_insights_object_type_case_insensitive5  s   $$mU%;<34 	NCJ)L%%22?((*(MF	N 6{a{a{ass66{a,,[9	N 	Ns   0E99FN)rL   rM   rN   rO   dictr   r  r  r  r  r  r  r  r  r  r  rP   r   r   r  r    sF    @$ 9 
=	;	5G
	<	=5
1	:r   r  __main__z-v).rO   builtinsr-   _pytest.assertion.rewrite	assertionrewriter+   syspathlibr   unittest.mockr   r   rE   requests.exceptionsr   r/  insertr   __file__parentutils.meta_ads_clientr	   r   fixturer   r   rR   rj   r   r   r   r   r   r  r  r#  r;  rK  re  rn  rt  r  r  rL   mainrP   r   r   <module>r     s    
  *  ) 3tH~,,334 5 01 459d3e 434 4 $ %= % %&  & \* *2'( '(T- -D/. /.n5 54!1 !1H;? ;?|9 9*8 8:5 5J#. #.L0, 0,f6 6"
7 
7$#- #-LL> L>hh: h:V zFKK4 ! 4 4 4 4s$   0	F9E: F:F	?FF