
    i0U                     <   d Z ddlZddlZddlZddlmZ ddlmZmZ ddl	Z	ej                  j                  d e ee      j                  j                  j                               e	j                  d        Ze	j                  d        Ze	j                  d        Ze	j                  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y)u   
utils/meta_ads_client.py 테스트 스위트

테스트 대상: MetaAdsClient 클래스
전략: 모든 외부 API 호출(facebook_business SDK, requests)을 mock으로 대체
    N)Path)	MagicMockpatchc               #      K   ddddd} t        j                  t        j                  | d      5  |  ddd       y# 1 sw Y   yxY ww)	u4   4개의 필수 환경변수를 세팅하는 fixture.test_app_idtest_app_secret
test_token
act_123456)META_APP_IDMETA_APP_SECRETMETA_ACCESS_TOKENMETA_AD_ACCOUNT_IDFclearN)r   dictosenviron)envs    Q/home/jay/workspace/.worktrees/task-2116-dev1/utils/tests/test_meta_ads_client.pymock_envr      sJ      %,)*	C 
BJJ5	1 	  s   .A
>	A
AA
c               #   T   K   t        d      5 } |  ddd       y# 1 sw Y   yxY ww)u7   FacebookAdsApi.init을 mock으로 대체하는 fixture.z)utils.meta_ads_client.FacebookAdsApi.initN)r   )	mock_inits    r   mock_api_initr   &   s,      
:	; y  s   (	(%(c               #   z   K   t        d      5 } t               }|| _        | |f ddd       y# 1 sw Y   yxY ww)u7   AdAccount 클래스를 mock으로 대체하는 fixture.utils.meta_ads_client.AdAccountN)r   r   return_value)mock_clsmock_instances     r   mock_ad_accountr   -   sA      
0	1 &X! -%%& & &s   ;/	;8;c                 x    t        d      5  ddlm}  |       }ddd       |\  }}|_        |S # 1 sw Y   xY w)u   
    MetaAdsClient 인스턴스를 반환하는 통합 fixture.
    load_env_keys와 FacebookAdsApi.init, AdAccount를 모두 mock 처리한다.
    #utils.meta_ads_client.load_env_keysr   MetaAdsClientN)r   utils.meta_ads_clientr#   _account)r   r   r   r#   instance_mock_ad_account_instances          r   clientr)   6   sF     
4	5 #7 ?#
 #2A0HO# #s   09c                       e Zd ZdZd Zd Zy)TestInitu"   MetaAdsClient.__init__() 테스트c                    t        d      5  t        d      5 }ddlm}  |       }|j                          |j                  }|\  }}t        |      t        |j                               z   }	d|	v s|j                  d      dk(  s
|d   dk(  sJ |j                          |j                  d   d   }
d|
v sJ 	 ddd       ddd       y# 1 sw Y   xY w# 1 sw Y   yxY w)	u_   환경변수 4개 모두 설정 시 정상 초기화되고 FacebookAdsApi.init이 호출된다.r!   r   r   r"   r   app_idr
   N)r   r$   r#   assert_called_once	call_argslistvaluesget)selfr   r   mock_ad_account_clsr#   r)   init_kwargsargskwargsall_argscall_args              r   test_init_successzTestInit.test_init_successN   s     78	,34	,8K;"_F ,,.'11K 'LD&DzD$99H H,

80D0UY]^_Y`dqYqqq  224*44Q7:H8+++)	, 	, 	, 	, 	, 	,s#   CBC7CC	CCc           	      |   t        j                  t        j                  i d      5  t        d      5  t        d      5  ddlm} t        j                  t        t        f      5   |        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   *xY w# 1 sw Y   yxY w)u:   필수 환경변수 누락 시 ValueError가 발생한다.Tr   r!   z$utils.meta_ads_client.FacebookAdsApir   r"   N)
r   r   r   r   r$   r#   pytestraises
ValueErrorKeyError)r3   r#   s     r   test_init_missing_envzTestInit.test_init_missing_envf   s     JJrzz2T2	 78	  89	 
 <
H56   	  	  	  	    	  	  	  	  	  	 sQ   B2B&&B%B	-B5B&=B2BBB#B&&B/	+B22B;N)__name__
__module____qualname____doc__r:   r@        r   r+   r+   K   s    ,,0 rF   r+   c                       e Zd ZdZd Zy)TestExchangeLongLivedTokenu   exchange_token() 테스트c                    t               }dddd|j                  _        t               |_        t	        d|      5 }|j                         }ddd       t        t              sJ |dk(  sJ j                          y# 1 sw Y   3xY w)u   
        requests.get을 mock하여 새 장기 토큰을 받고,
        반환값이 토큰 문자열인지 확인한다.
        FacebookAdsApi.init 재호출은 exchange_token 내부에서 일어나지 않는다.
        long_token_xxxbeareri O )access_token
token_type
expires_in"utils.meta_ads_client.requests.getr   N)	r   jsonr   raise_for_statusr   exchange_token
isinstancestrr.   )r3   r)   r   r   mock_responsemock_getresults          r   test_exchange_long_lived_tokenz9TestExchangeLongLivedToken.test_exchange_long_lived_token|   s     ","!+
'
 *3&7mT 	-X`**,F	- &#&&&)))) 	##%	- 	-s   A??BN)rA   rB   rC   rD   rY   rE   rF   r   rH   rH   y   s
    $&rF   rH   c                       e Zd ZdZd Zy)TestGetTokenInfou   check_token() 테스트c                 N   dddddddgdi}t               }||j                  _        t               |_        t	        d	|
      5  |j                         }ddd       t        t              sJ |j                  d      du sJ |j                  d      dk(  sJ y# 1 sw Y   GxY w)uH   requests.get mock으로 토큰 정보 dict를 올바르게 반환한다.dataTiIr   999ads_readads_management)is_valid
expires_atr-   user_idscopesrO   rP   Nra   rb   )	r   rQ   r   rR   r   check_tokenrT   r   r2   )r3   r)   expected_datarV   rX   s        r   test_get_token_infoz$TestGetTokenInfo.test_get_token_info   s      (' %'78
 "*7')2&7mT 	*'')F	* &$'''zz*%---zz,':555	* 	*s   BB$N)rA   rB   rC   rD   rg   rE   rF   r   r[   r[      s
    !6rF   r[   c                       e Zd ZdZd Zy)TestGetAccountInfou   get_account_info() 테스트c                     ddddd}||j                   j                  _        |j                         }|J |j                   j                  j	                          y)uH   AdAccount.api_get mock으로 계정 필드를 올바르게 반환한다.r
   zTest Ad AccountKRW   )idnamecurrencyaccount_statusN)r%   api_getr   get_account_infor.   )r3   r)   expectedrX   s       r   test_get_account_infoz(TestGetAccountInfo.test_get_account_info   s[     %	
 08,((*!!!224rF   N)rA   rB   rC   rD   rt   rE   rF   r   ri   ri      s
    &5rF   ri   c                       e Zd ZdZd Zy)TestListCampaignsu   list_campaigns() 테스트c                 >   t               }t        d       |_        t               }t        d       |_        ||g|j                  j                  _        |j                         }|J t        |      dk(  sJ |j                  j                  j                          y)uG   AdAccount.get_campaigns mock으로 캠페인 리스트를 반환한다.c                     ddd|    S )Ncamp_001z
Campaign 1rm   rn   rE   ks    r   <lambda>z7TestListCampaigns.test_list_campaigns.<locals>.<lambda>       ZamFnopFq rF   )side_effectc                     ddd|    S )Ncamp_002z
Campaign 2rz   rE   r{   s    r   r}   z7TestListCampaigns.test_list_campaigns.<locals>.<lambda>   r~   rF   N   )r   __getitem__r%   get_campaignsr   list_campaignslenr.   )r3   r)   mock_campaign_1mock_campaign_2rX   s        r   test_list_campaignsz%TestListCampaigns.test_list_campaigns   s    #+&/<q&r##+&/<q&r#6E5W%%2&&(!!!6{a%%88:rF   N)rA   rB   rC   rD   r   rE   rF   r   rv   rv      s
    $;rF   rv   c                       e Zd ZdZd Zd Zy)TestCreateCampaignu   create_campaign() 테스트c                 f   t               }dddd|j                  _        ||j                  j                  _        |j	                  dd      }|j                  j                  j                          |j                  j                  j                  \  }}t        |      t        |      z   }d|v sJ y)uh   account.create_campaign mock으로 캠페인을 생성하고 기본 status가 PAUSED임을 확인한다.camp_new_001zNew CampaignPAUSEDrm   rn   statusOUTCOME_AWARENESSrn   	objectiveN)r   export_all_datar   r%   create_campaignr.   r/   rU   )r3   r)   mock_campaignrX   r/   call_kwargsall_args_strs          r   test_create_campaignz'TestCreateCampaign.test_create_campaign   s    ! "6
%%2
 8E''4'') ( 
 	''::< "(!@!@!J!J	;9~K(88<'''rF   c                    t               }dddd|j                  _        ||j                  j                  _        |j	                  dd       |j                  j                  j
                  \  }}t        |      t        |      z   }d|v sJ y)u<   status 파라미터 미지정 시 기본값이 PAUSED이다.camp_new_002zTest Campaignr   r   OUTCOME_TRAFFICr   N)r   r   r   r%   r   r/   rU   )r3   r)   r   r/   r   r   s         r   *test_create_campaign_default_status_pausedz=TestCreateCampaign.test_create_campaign_default_status_paused  s    ! #6
%%2
 8E''4O?PQ "(!@!@!J!J	;9~K(88<'''rF   N)rA   rB   rC   rD   r   r   rE   rF   r   r   r      s    %(.(rF   r   c                       e Zd ZdZd Zy)TestUpdateCampaignu   update_campaign() 테스트c                     t        d      5 }t               }||_        ||j                  _        |j	                  ddd      }|j                  j                          ddd       y# 1 sw Y   yxY w)uF   Campaign.api_update mock으로 캠페인 업데이트를 수행한다.utils.meta_ads_client.Campaignry   zUpdated CampaignACTIVE)campaign_idrn   r   N)r   r   r   
api_updateupdate_campaignr.   r3   r)   mock_campaign_clsr   rX   s        r   test_update_campaignz'TestUpdateCampaign.test_update_campaign  ss    34 	:8I%KM-:*4AM$$1++&' , F $$779	: 	: 	:s   AA&&A/N)rA   rB   rC   rD   r   rE   rF   r   r   r     s
    %:rF   r   c                       e Zd ZdZd Zy)TestDeleteCampaignu   delete_campaign() 테스트c                     t        d      5 }t               }||_        d|j                  _        |j	                  d      }|j                  j                          |du sJ 	 ddd       y# 1 sw Y   yxY w)uN   Campaign.api_delete mock으로 캠페인을 삭제하고 True를 반환한다.r   Try   )r   N)r   r   r   
api_deletedelete_campaignr.   r   s        r   test_delete_campaignz'TestDeleteCampaign.test_delete_campaign6  sr    34 		"8I%KM-:*48M$$1++
+CF$$779T>!>		" 		" 		"s   AA++A4N)rA   rB   rC   rD   r   rE   rF   r   r   r   3  s
    %"rF   r   c                       e Zd ZdZd Zd Zy)TestUploadImageu   upload_image() 테스트c                 ~   t               }ddi|j                  _        ||j                  j                  _        t        d      5 }t               }||_        d|j                  _        d|_        t        d      |_        |j                  d      }d	d	d	       t        t              sJ |dk(  sJ y	# 1 sw Y   #xY w)
uO   AdImage mock으로 이미지를 업로드하고 hash 문자열을 반환한다.hashabc123def456utils.meta_ads_client.PathTtest_banner.png/tmp/test_banner.pngrP   
image_pathNr   r   r   r%   create_ad_imager   existsrn   __str__upload_imagerT   rU   r3   r)   
mock_imagemock_path_clsmock_path_instancerX   s         r   test_upload_imagez!TestUploadImage.test_upload_imageL  s    [
39>2J
""/7A''4/0 	LM!*);M&59%%2&7#)2@V)W&((4J(KF	L &#&&&'''	L 	Ls   AB33B<c                 z   t               }dddddii|j                  _        ||j                  j                  _        t        d      5 }t               }||_        d|j                  _        d|_        t        d	      |_        |j                  d
      }ddd       t        t              sJ y# 1 sw Y   xY w)uM   upload_image는 hash 문자열만 반환한다 (중첩 응답 처리 포함).imagesr   r   zhttps://example.com/image.jpg)r   urlr   Tr   rP   r   Nr   r   s         r   &test_upload_image_with_nested_responsez6TestUploadImage.test_upload_image_with_nested_response_  s    [
!*:$3

""/ 8B''4/0 	LM!*);M&59%%2&7#)2@V)W&((4J(KF	L &#&&&	L 	Ls   	AB11B:N)rA   rB   rC   rD   r   r   rE   rF   r   r   r   I  s    "(&'rF   r   c                       e Zd ZdZd Zd Zy)TestGetInsightsu   get_insights() 테스트c                 N   t               }dddddd|j                  _        t        d      5 }t               }||_        |g|j                  _        |j	                  dd	
      }ddd       J t        |      dk(  sJ j                  j                          y# 1 sw Y   8xY w)uH   Campaign.get_insights mock으로 인사이트 데이터를 반환한다.10000500500008000z5.0)impressionsclicksspendreachctrr   123campaign	object_idobject_typeNrl   )r   r   r   r   get_insightsr   r.   )r3   r)   mock_insight_1r   mock_campaign_instancerX   s         r   test_get_insightsz!TestGetInsights.test_get_insights  s    ""7
&&3 34 	R8I%.["-C*@N?O"//<((5j(QF	R !!!6{a++>>@	R 	Rs   7BB$c                 >   t        d      5 }t               }||_        g |j                  _        |j                  dd      }|j                  j                  \  }}t        |      t        |      z   t        fddD              sJ 	 ddd       y# 1 sw Y   yxY w)uW   기본 필드(impressions, clicks, spend)를 포함하여 인사이트를 조회한다.r   r   r   r   c              3   &   K   | ]  }|v  
 y w)NrE   ).0fieldr   s     r   	<genexpr>z@TestGetInsights.test_get_insights_with_fields.<locals>.<genexpr>  s     fu,fs   )r   r   r   r   N)r   r   r   r   r/   rU   any)r3   r)   r   r   rX   r/   r   r   s          @r   test_get_insights_with_fieldsz-TestGetInsights.test_get_insights_with_fields  s    34 	g8I%.["-C*?A"//<((5j(QF%;%H%H%R%R"I{y>C,<<Lf:effff	g 	g 	gs   A<BBN)rA   rB   rC   rD   r   r   rE   rF   r   r   r   ~  s    "A,grF   r   c                       e Zd ZdZd Zd Zy)TestUpdateEnvTokenu   update_env_token() 테스트c                 ~   d}d}t        j                  ddd      5 }|j                  |       |j                  }ddd       	 |_        |j                  |       t        |      j                         }||v sJ d	|vsJ 	 t        j                  |       y# 1 sw Y   _xY w# t        j                         w xY w)
u   
        실제 임시 파일을 사용하여 META_ACCESS_TOKEN 라인이 새 토큰으로 교체되는지 확인한다.
        export META_APP_ID=test_app_id
export META_APP_SECRET=test_app_secret
export META_ACCESS_TOKEN=old_token
export META_AD_ACCOUNT_ID=act_123456
new_long_token_xyzw.keysFmodesuffixdeleteN	new_token	old_token
tempfileNamedTemporaryFilewritern   _env_keys_pathupdate_env_tokenr   	read_textr   unlinkr3   r)   original_contentr   tmptmp_pathupdated_contents          r   #test_update_env_token_replaces_linez6TestUpdateEnvToken.test_update_env_token_replaces_line  s    
5 	 )	((c'%P 	 TWII&'xxH	 		 $,F!##i#8"8n668O///o555IIh	  	  IIh   B>B% B"%B<c                 ~   d}d}t        j                  ddd      5 }|j                  |       |j                  }ddd       	 |_        |j                  |       t        |      j                         }||v sJ d	|vsJ 	 t        j                  |       y# 1 sw Y   _xY w# t        j                         w xY w)
uU   실제 임시 파일을 사용하여 META_ACCESS_TOKEN 라인 교체를 검증한다.r   r   r   r   Fr   Nr   r   r   r   s          r   (test_update_env_token_with_real_tempfilez;TestUpdateEnvToken.test_update_env_token_with_real_tempfile  s    5 	 )	((c'%P 	 TWII&'xxH	 		 $,F!##i#8"8n668O///o555IIh	  	  IIhr   N)rA   rB   rC   rD   r   r  rE   rF   r   r   r     s    & 6 rF   r   )rD   r   sysr   pathlibr   unittest.mockr   r   r<   pathinsertrU   __file__parentfixturer   r   r   r)   r+   rH   r[   ri   rv   r   r   r   r   r   r   rE   rF   r   <module>r
     s#   
 
   *  3tH~,,33::; < 	 	   & &  (&  & \& &B6 6@5 50; ;0)( )(b: :0" ",-' -'j&g &g\5  5 rF   