
    y!i                     n   d 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mZ ddlZej                  j!                  d e e
e      j&                  j&                               dedej(                  fdZdD ]
  Z ee         e       Zeej0                  d	   _         ed
efi       Zeej0                  d   _         ei       ej0                  d   _         e       ej0                  d   _         ed      5   ed      5 Z  e       e jB                  _"        ddl#mZ ddd       ddd       ejH                  d        Z%ejH                  d        Z&ejH                  d        Z'defdZ(de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%      Z0 G d& d'      Z1 G d( d)      Z2 G d* d+      Z3 G d, d-      Z4 G d. d/      Z5 G d0 d1      Z6 G d2 d3      Z7e8d4k(  r ejr                  ed5g       yy# 1 sw Y   xY w# 1 sw Y   xY w)6u  
Google Ads API 클라이언트 단위 테스트 (Mock 기반)

테스트 대상: GoogleAdsClient (utils.google_ads_client)

테스트 항목:
- 초기화: 환경변수 정상/누락 시 동작
- 캠페인 CRUD: list, get, create, update, delete
- 광고그룹 CRUD: list, create, update, delete
- 키워드 관리: list, add, update_status
- 인사이트: get_insights (campaign/ad_group/keyword), 잘못된 entity_type ValueError
- RSA 광고 생성: create_responsive_search_ad (유효성 검증 포함)
- 인터페이스 일관성: MetaAdsClient와 공통 메서드명 확인

모든 외부 호출(Google Ads SDK)은 Mock으로 대체하여
실제 Google Ads API를 호출하지 않는다.
    N)Path)	MagicMockpatchdotted_namereturnc                    | j                  d      }t        dt        |      dz         D ]  }dj                  |d|       }|t        j
                  vs*t        j                  |      }|dkD  r;dj                  |d|dz
         }t        t        j
                  |   ||dz
     |       |t        j
                  |<    t        j
                  |    S )u_   dotted_name 경로의 가짜 모듈을 sys.modules에 등록(없을 때만)하고 반환한다..   N)	splitrangelenjoinsysmodulestypes
ModuleTypesetattr)r   partsikeymod
parent_keys         3/home/jay/workspace/tests/test_google_ads_client.py_ensure_fake_moduler   &   s    c"E1c%j1n% #hhuRay!ckk!""3'C1u XXeGa!en5
J/q1usC"CKK# ;;{##    )googlez
google.adszgoogle.ads.googleadsgoogle.ads.googleads.clientgoogle.ads.googleads.errorszgoogle.protobufgoogle.protobuf.json_formatgoogle.protobuf.field_mask_pb2r   GoogleAdsExceptionr   )return_valuer   r    %utils.google_ads_client.load_env_keys(utils.google_ads_client._GoogleAdsClient)GoogleAdsClientc                     | j                  dd       | j                  dd       | j                  dd       | j                  dd       | j                  d	d
       y)u?   5개의 필수 Google Ads 환경변수를 세팅하는 fixture.GOOGLE_ADS_DEVELOPER_TOKENztest-dev-tokenGOOGLE_ADS_CLIENT_IDztest-client-idGOOGLE_ADS_CLIENT_SECRETztest-client-secretGOOGLE_ADS_REFRESH_TOKENztest-refresh-tokenGOOGLE_ADS_CUSTOMER_ID
1234567890N)setenv)monkeypatchs    r   mock_envr/   \   s]     35EF-/?@13GH13GH/>r   c               #      K   t        d      5 } t               }|| j                  _        | ddd       y# 1 sw Y   yxY ww)uI   Google Ads SDK 클라이언트 인스턴스 mock을 반환하는 fixture.r$   N)r   r   load_from_dictr"   )mock_clsmock_instances     r   mock_sdk_clientr4   f   sB      
9	: h!/<,  s   A 7	AA Ac                 X    t        d      5  t               cddd       S # 1 sw Y   yxY w)uB   테스트용 GoogleAdsClient 인스턴스를 제공하는 fixture.r#   N)r   r%   )r/   r4   s     r   clientr6   o   s(     
6	7 ! ! ! !s   
 )c                      t               }| j                         D ]>  \  }}|j                  d      }|}|dd D ]  }t        ||      } t	        ||d   |       @ |S )uC   GoogleAdsService.search() 한 행(row) mock을 생성하는 헬퍼.r	   N)r   itemsr   getattrr   )kwargsrow	attr_pathvaluer   objparts          r   _make_search_rowrA   v   sn    
+C"LLN '	5$#2J 	%D#t$C	%U2Y&' Jr   resource_namesc                      t               }| D cg c]  }t                c}|_        t        |j                  |       D ]  \  }}||_         |S c c}w )u0   mutate_xxx() 응답 mock을 생성하는 헬퍼.)r   resultszipresource_name)rB   result_mock_mock_rrns        r   _make_mutate_resultrK      sR    +K0>?19;?K+--~> "
!" @s   Ac                   "    e Zd ZdZd Zd Zd Zy)TestGoogleAdsClientInitu0   GoogleAdsClient.__init__ 초기화 동작 검증c                    t        d      5  t               }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                  }||u }|st        j                  d	|fd
||f      dt	        j
                         v st        j                  |      rt        j                  |      nd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)uM   필수 환경변수가 모두 설정된 경우 예외 없이 초기화된다.r#   N)is not)z%(py0)s is not %(py3)scpy0py3assert %(py5)spy5is)z/%(py2)s
{%(py2)s = %(py0)s._client
} is %(py4)sr4   rR   py2py4assert %(py6)spy6)r   r%   
@pytest_ar_call_reprcompare@py_builtinslocals_should_repr_global_name	_safereprAssertionError_format_explanation_client)selfr/   r4   rP   @py_assert2@py_assert1@py_format4@py_format6@py_assert3@py_format5@py_format7s              r   test_init_successz)TestGoogleAdsClientInit.test_init_success   s    :; 	"!A	" q}qqqyy+yO++++yO++++++q+++q+++y++++++O+++O+++++++	" 	"s   F55F?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)uJ   모든 필수 환경변수가 누락된 경우 ValueError가 발생한다.)r'   r(   r)   r*   r+   Fraisingr#   r$   Ndelenvr   pytestraises
ValueErrorr%   )rf   r.   r   s      r   test_init_missing_envz-TestGoogleAdsClientInit.test_init_missing_env   s    
 	3C sE2	3 :; 	"UCm=n 	"z* "!"	" 	" 	"" "	" 	" 	" 	"s:   BA<A0A<B0A95A<<B	BBc                    |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   단일 필수 환경변수(GOOGLE_ADS_DEVELOPER_TOKEN)가 누락되면 ValueError가 발생한다.r'   Frp   r#   r$   matchNrr   )rf   r/   r.   s      r   /test_init_missing_single_env_raises_value_errorzGTestGoogleAdsClientInit.test_init_missing_single_env_raises_value_error   s    7G:; 	"UCm=n 	"z1MN "!"	" 	" 	"" "	" 	" 	" 	"s:   BA7A+A7B+A40A77B 	<BBN)__name__
__module____qualname____doc__rn   rw   r{    r   r   rM   rM      s    :,""r   rM   c                       e Zd ZdZd Zd Zy)TestGetAccountInfou2   get_account_info: 계정 정보 dict 반환 검증c                    t               }||j                  _        t               }d|j                  _        d|j                  _        d|j                  _        d|j                  _        d|j                  _        d|j                  j                  _
        |g|j                  _        |j                         }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}|j                  j-                          y)u+   get_account_info()는 dict를 반환한다.iIu   테스트 계정KRWz
Asia/SeoulTENABLED5assert %(py4)s
{%(py4)s = %(py0)s(%(py1)s, %(py2)s)
}
isinstanceresultdictrR   py1rY   rZ   N)r   get_servicer"   customeriddescriptive_namecurrency_code	time_zoneauto_tagging_enabledstatusnamesearchget_account_infor   r   r_   r`   r]   ra   rb   rc   rd   assert_called_oncerf   r6   r4   mock_ga_servicer<   r   rk   rl   s           r   "test_get_account_info_returns_dictz5TestGetAccountInfo.test_get_account_info_returns_dict   s#   #+3B##0k$(:%%*"!-,0)#, /2e+((*&$''''''''z'''z''''''&'''&''''''$'''$''''''''''113r   c                    t               }||j                  _        g |j                  _        |j	                         }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)u8   API 응답이 비어 있으면 빈 dict를 반환한다.==z%(py0)s == %(py3)sr   rQ   rT   rU   N)r   r   r"   r   r   r]   r^   r_   r`   ra   rb   rc   rd   	rf   r6   r4   r   r   rg   rh   ri   rj   s	            r   7test_get_account_info_empty_response_returns_empty_dictzJTestGetAccountInfo.test_get_account_info_empty_response_returns_empty_dict   s    #+3B##0.0+((*v|vvvr   N)r|   r}   r~   r   r   r   r   r   r   r   r      s    <4&r   r   c                   (    e Zd ZdZd Zd Zd Zd Zy)TestListCampaignsu(   list_campaigns: list[dict] 반환 검증c                    t               }||j                  _        t               }d|j                  _        d|j                  _        d|j                  j                  _        d|j                  j                  _        d|j                  _        d|j                  _	        d|j                  _        t               }d|j                  _        d	|j                  _        d
|j                  j                  _        d|j                  j                  _        d|j                  _        d|j                  _	        d|j                  _        ||g|j                  _        |j                         }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}
|	|
k(  }|st%        j0                  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        }t3        |      }|sddt!        j"                         v st%        j&                  t2              rt%        j(                  t2              ndt%        j(                  |      t%        j(                  |      dz  }t+        t%        j,                  |            dx}}y)u7   list_campaigns() 호출 시 list[dict]를 반환한다.o      캠페인 알파r   SEARCH
2024-01-01 @B    u   캠페인 베타PAUSEDz
2024-02-01逄 r   r   r   listr   N   r   z0%(py3)s
{%(py3)s = %(py0)s(%(py1)s)
} == %(py6)sr   rR   r   rS   r\   assert %(py8)spy8c              3   <   K   | ]  }t        |t                y wNr   r   .0items     r   	<genexpr>z8TestListCampaigns.test_list_campaigns.<locals>.<genexpr>        =d:dD)=   ,assert %(py4)s
{%(py4)s = %(py0)s(%(py2)s)
}allrX   )r   r   r"   campaignr   r   r   advertising_channel_type
start_dateend_datecampaign_budgetamount_microsr   list_campaignsr   r   r_   r`   r]   ra   rb   rc   rd   r   r^   r   )rf   r6   r4   r   row1row2r   rk   rl   rg   @py_assert5@py_assert4rm   @py_format9rh   s                  r   test_list_campaignsz%TestListCampaigns.test_list_campaigns   s|   #+3B##0{/$-!6>..3#/ !#-4*{/$,!6>..3#/ !#-4*/3Tl+&&(&$''''''''z'''z''''''&'''&''''''$'''$''''''''''6{a{a{ass66{a=f==s=========s===s==============r   c           	         t               }||j                  _        g |j                  _        |j	                          |j                  j
                  \  }}d}|j                  }d}d}	 |||	      }
||
v }|st        j                  d|fd||
f      t        j                  |      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}x}	}
y
)u0   기본 limit(25)이 GAQL 쿼리에 포함된다.25queryr   inzT%(py1)s in %(py11)s
{%(py11)s = %(py5)s
{%(py5)s = %(py3)s.get
}(%(py7)s, %(py9)s)
}r;   r   rS   rU   py7py9py11assert %(py13)spy13Nr   r   r"   r   r   	call_argsgetr]   r^   rb   r_   r`   ra   rc   rd   rf   r6   r4   r   rH   r;   @py_assert0r   @py_assert6@py_assert8@py_assert10rg   @py_format12@py_format14s                 r   *test_list_campaigns_default_limit_in_queryz<TestListCampaigns.test_list_campaigns_default_limit_in_query  s    #+3B##0.0+#**44	6.vzz.'.2.z'2..t.....t....t......v...v...z...'...2............r   c           	         t               }||j                  _        g |j                  _        |j	                  d       |j                  j
                  \  }}d}|j                  }d}d}	 |||	      }
||
v }|st        j                  d|fd||
f      t        j                  |      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}x}	}
y)u:   사용자 지정 limit(10)이 GAQL 쿼리에 포함된다.
   )limit10r   r   r   r   r;   r   r   r   Nr   r   s                 r   )test_list_campaigns_custom_limit_in_queryz;TestListCampaigns.test_list_campaigns_custom_limit_in_query  s    #+3B##0.0+B'#**44	6.vzz.'.2.z'2..t.....t....t......v...v...z...'...2............r   c                    t               }||j                  _        g |j                  _        |j	                         }g }||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)u5   캠페인이 없으면 빈 리스트를 반환한다.r   r   r   rQ   rT   rU   N)r   r   r"   r   r   r]   r^   r_   r`   ra   rb   rc   rd   r   s	            r   ,test_list_campaigns_empty_returns_empty_listz>TestListCampaigns.test_list_campaigns_empty_returns_empty_list  s    #+3B##0.0+&&(v|vvvr   N)r|   r}   r~   r   r   r   r   r   r   r   r   r   r      s    2>>	/	/r   r   c                   "    e Zd ZdZd Zd Zd Zy)TestGetCampaignu    get_campaign: dict 반환 검증c                    t               }||j                  _        t               }d|j                  _        d|j                  _        d|j                  j                  _        d|j                  j                  _        d|j                  _        d|j                  _	        d|j                  _        |g|j                  _        |j                  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}y)u/   get_campaign() 호출 시 dict를 반환한다.r   u   대상 캠페인r   r   r   r   i- 111r   r   r   r   r   N)r   r   r"   r   r   r   r   r   r   r   r   r   r   get_campaignr   r   r_   r`   r]   ra   rb   rc   rd   r   s           r   test_get_campaignz!TestGetCampaign.test_get_campaign&  s'   #+3B##0k.#, 5=--2". ",3)/2e+$$U+&$''''''''z'''z''''''&'''&''''''$'''$''''''''''r   c                     t               }||j                  _        g |j                  _        t	        j
                  t        d      5  |j                  d       ddd       y# 1 sw Y   yxY w)uF   존재하지 않는 campaign_id 조회 시 ValueError가 발생한다.u$   캠페인을 찾을 수 없습니다ry   999999N)r   r   r"   r   rt   ru   rv   r   )rf   r6   r4   r   s       r   .test_get_campaign_not_found_raises_value_errorz>TestGetCampaign.test_get_campaign_not_found_raises_value_error9  sW    #+3B##0.0+]]:-ST 	*)	* 	* 	*s   A##A,c           	         t               }||j                  _        t               }d|j                  _        d|j                  _        d|j                  j                  _        d|j                  j                  _        d|j                  _        d|j                  _	        d|j                  _        |g|j                  _        |j                  d       |j                  j                  \  }}d}|j                  }d	}	d}
 ||	|
      }||v }|st!        j"                  d
|fd||f      t!        j$                  |      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}	x}
}y)uH   get_campaign()이 campaign_id를 WHERE 절에 포함하여 조회한다.i+  u   ID 포함 쿼리 확인r   r   r   r     555r   r   r   r;   r   r   r   N)r   r   r"   r   r   r   r   r   r   r   r   r   r   r   r   r   r]   r^   rb   r_   r`   ra   rc   rd   )rf   r6   r4   r   r<   rH   r;   r   r   r   r   r   rg   r   r   s                  r   &test_get_campaign_includes_id_in_queryz6TestGetCampaign.test_get_campaign_includes_id_in_queryB  s\   #+3B##0k5#+ 5=--2". ",2)/2e+E"#**44	6/

/7/B/
7B//u/////u////u////////////
///7///B////////////r   N)r|   r}   r~   r   r   r   r   r   r   r   r   r   #  s    *(&*0r   r   c                   .    e Zd ZdZd Zd Zd Zd Zd Zy)TestCreateCampaignu#   create_campaign: dict 반환 검증c                    t               t               t        d      }|j                  _        t        d      }|j                  _        fd}||j
                  _        t               |j                  _        fS )uX   create_campaign에 필요한 CampaignBudgetService/CampaignService mock을 설정한다.z!customers/123/campaignBudgets/456zcustomers/123/campaigns/789c                     | dk(  rS S )NCampaignBudgetServicer   )service_namemock_budget_servicemock_campaign_services    r   get_service_side_effectzGTestCreateCampaign._setup_create_mocks.<locals>.get_service_side_effecte  s    66**((r   )r   rK   mutate_campaign_budgetsr"   mutate_campaignsr   side_effectget_type)rf   r4   mock_budget_resultmock_campaign_resultr   r   r   s        @@r   _setup_create_mocksz&TestCreateCampaign._setup_create_mocksZ  s{    'k )01TUCU33@23PQ>R..;	)
 3J##/09  -"$999r   c                    | j                  |       |j                  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
}y
)u2   create_campaign() 호출 시 dict를 반환한다.u   새 캠페인r   r   )r   budget_amountr   r   r   r   r   r   N)r  create_campaignr   r   r_   r`   r]   ra   rb   rc   rd   rf   r6   r4   r   rk   rl   s         r   test_create_campaignz'TestCreateCampaign.test_create_campaignn  s      1'' ! ( 
 &$''''''''z'''z''''''&'''&''''''$'''$''''''''''r   c                 t   | 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}}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}}y)uQ   create_campaign() 반환 dict에 id, resource_name, name, status가 포함된다.u   키 확인 캠페인r   r   r  r   r   z%(py1)s in %(py3)sr   r   rS   rT   rU   NrF   r   r   )
r  r  r]   r^   rb   r_   r`   ra   rc   rd   rf   r6   r4   r   r   rg   ri   rj   s           r   2test_create_campaign_result_contains_expected_keyszETestCreateCampaign.test_create_campaign_result_contains_expected_keysz  s     1''-CSZ'[tv~tvtvv(&((((&(((((((((&(((&(((((((vvv!x6!!!!x6!!!x!!!!!!6!!!6!!!!!!!r   c                 H   | j                  |       |j                  d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)N   status 인자를 생략하면 기본값 PAUSED가 반환 dict에 포함된다.u   기본상태 캠페인r   r  r   r   r   zI%(py6)s
{%(py6)s = %(py2)s
{%(py2)s = %(py0)s.get
}(%(py4)s)
} == %(py9)sr   rR   rY   rZ   r\   r   assert %(py11)sr   N)r  r  r   r]   r^   r_   r`   ra   rb   rc   rd   )rf   r6   r4   r   rh   rk   r   r   @py_assert7@py_format10r   s              r   *test_create_campaign_default_status_pausedz=TestCreateCampaign.test_create_campaign_default_status_paused  s      1''-EU['\zz/(/z(#/x/#x////#x//////v///v///z///(///#///x////////r   c                     | j                  |      \  }}|j                  dd       |j                  j                          |j                  j                          y)uU   create_campaign()이 예산 생성 후 캠페인 생성을 순서대로 호출한다.u   순서 확인 캠페인r   r  N)r  r  r   r   r   )rf   r6   r4   mock_budget_svcmock_campaign_svcs        r   7test_create_campaign_calls_budget_and_campaign_serviceszJTestCreateCampaign.test_create_campaign_calls_budget_and_campaign_services  sP    -1-E-Eo-V**$=WU//BBD**==?r   N)	r|   r}   r~   r   r  r
  r  r  r  r   r   r   r   r   W  s    -:(
(	"0@r   r   c                   (    e Zd ZdZd Zd Zd Zd Zy)TestUpdateCampaignu#   update_campaign: dict 반환 검증c                    t               }||j                  _        t               |j                  _        t	        d      }||j
                  _        |j                  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}y)u2   update_campaign() 호출 시 dict를 반환한다.zcustomers/123/campaigns/111r   u   업데이트된 캠페인r   r   r   r   r   r   r   r   N)r   r   r"   r  rK   r   update_campaignr   r   r_   r`   r]   ra   rb   rc   rd   )rf   r6   r4   r   mock_responser   rk   rl   s           r   test_update_campaignz'TestUpdateCampaign.test_update_campaign  s     )3H##009  -+,IJ>K..;''4OXa'b&$''''''''z'''z''''''&'''&''''''$'''$''''''''''r   c                 \   t               }||j                  _        t               |j                  _        t	        d      |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}}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)uF   update_campaign() 반환 dict에 id와 updated_fields가 포함된다.zcustomers/123/campaigns/222222u   필드 확인r   r   r   r   r  r   r  rT   rU   Nupdated_fields)r   r   r"   r  rK   r   r!  r]   r^   rb   r_   r`   ra   rc   rd   )	rf   r6   r4   mock_svcr   r   rg   ri   rj   s	            r   :test_update_campaign_result_contains_id_and_updated_fieldszMTestUpdateCampaign.test_update_campaign_result_contains_id_and_updated_fields  s   ;3;##009  -1DEb1c!!.''OH'Utv~tvtvv)6))))6)))))))))6)))6)))))))r   c                    t               }||j                  _        t               |j                  _        t	        d      |j
                  _        |j                  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}}y
)u2   delete_campaign() 호출 시 True를 반환한다.zcustomers/123/campaigns/333333TrV   z%(py0)s is %(py3)sr   rQ   rT   rU   N)r   r   r"   r  rK   r   delete_campaignr]   r^   r_   r`   ra   rb   rc   rd   )	rf   r6   r4   r'  r   rg   rh   ri   rj   s	            r   test_delete_campaignz'TestUpdateCampaign.test_delete_campaign  s    ;3;##009  -1DEb1c!!.''.v~vvvr   c                     t               }||j                  _        t               |j                  _        t	        d      |j
                  _        |j                  d       |j
                  j                          y)uF   delete_campaign()이 CampaignService.mutate_campaigns를 호출한다.zcustomers/123/campaigns/444444N)r   r   r"   r  rK   r   r,  r   )rf   r6   r4   r'  s       r   +test_delete_campaign_calls_mutate_campaignsz>TestUpdateCampaign.test_delete_campaign_calls_mutate_campaigns  s[    ;3;##009  -1DEb1c!!.u%!!446r   N)r|   r}   r~   r   r#  r(  r-  r0  r   r   r   r  r    s    -(
*		7r   r  c                   "    e Zd ZdZd Zd Zd Zy)TestListAdGroupsu(   list_ad_groups: list[dict] 반환 검증c                 F   t               }||j                  _        t               }d|j                  _        d|j                  _        d|j                  j                  _        d|j                  j                  _        d|j                  _        d|j                  _        d|j                  _        |g|j                  _        |j                         }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}d |D        }t+        |      }|sddt        j                         v st!        j"                  t*              rt!        j$                  t*              ndt!        j$                  |      t!        j$                  |      dz  }t'        t!        j(                  |            dx}}y)u7   list_ad_groups() 호출 시 list[dict]를 반환한다.r   u   광고그룹 Ar   SEARCH_STANDARDr   r   r   r   r   r   r   r   Nc              3   <   K   | ]  }t        |t                y wr   r   r   s     r   r   z7TestListAdGroups.test_list_ad_groups.<locals>.<genexpr>  r   r   r   r   rX   )r   r   r"   ad_groupr   r   r   type_cpc_bid_microsr   r   list_ad_groupsr   r   r_   r`   r]   ra   rb   rc   rd   r   	rf   r6   r4   r   r<   r   rk   rl   rh   s	            r   test_list_ad_groupsz$TestListAdGroups.test_list_ad_groups  s   #+3B##0k,#, "3&-#./2e+&&(&$''''''''z'''z''''''&'''&''''''$'''$''''''''''=f==s=========s===s==============r   c           	         t               }||j                  _        g |j                  _        |j	                  d       |j                  j
                  \  }}d}|j                  }d}d}	 |||	      }
||
v }|st        j                  d|fd||
f      t        j                  |      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}x}	}
y)uT   campaign_id가 지정되면 GAQL 쿼리에 WHERE campaign.id 조건이 포함된다.999campaign_idr   r   r   r   r;   r   r   r   Nr   r   r"   r   r9  r   r   r]   r^   rb   r_   r`   ra   rc   rd   r   s                 r   -test_list_ad_groups_with_campaign_id_in_queryz>TestListAdGroups.test_list_ad_groups_with_campaign_id_in_query  s    #+3B##0.0+%0#**44	6/

/7/B/
7B//u/////u////u////////////
///7///B////////////r   c           	         t               }||j                  _        g |j                  _        |j	                  d       |j                  j
                  \  }}d}|j                  }d}d}	 |||	      }
||
v}|st        j                  d|fd||
f      t        j                  |      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}x}	}
y)uO   campaign_id가 None이면 WHERE 절 없이 전체 광고그룹을 조회한다.Nr>  zWHERE campaign.idr   r   )not in)zX%(py1)s not in %(py11)s
{%(py11)s = %(py5)s
{%(py5)s = %(py3)s.get
}(%(py7)s, %(py9)s)
}r;   r   r   r   r@  r   s                 r   7test_list_ad_groups_without_campaign_id_no_where_clausezHTestListAdGroups.test_list_ad_groups_without_campaign_id_no_where_clause  s    #+3B##0.0+$/#**44	6"A&**AWAbA*Wb*AA"*AAAAA"*AAAA"AAAAAA&AAA&AAA*AAAWAAAbAAA*AAAAAAAAAr   N)r|   r}   r~   r   r;  rA  rD  r   r   r   r2  r2    s    2>(	0
Br   r2  c                   (    e Zd ZdZd Zd Zd Zd Zy)TestCreateAdGroupu#   create_ad_group: dict 반환 검증c                 .   t               t               fd}||j                  _        t               |j                  _        t        d      j                  _        |j                  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}y)u2   create_ad_group() 호출 시 dict를 반환한다.c                 4    | dk(  rS | dk(  rS t               S NCampaignServiceAdGroupServicer   r   mock_ad_group_svcr  s    r   r   zGTestCreateAdGroup.test_create_ad_group.<locals>.get_service_side_effect  )    ((((''((;r   customers/123/adGroups/456r   u   새 광고그룹r   r   )r?  r   cpc_bidr   r   r   r   r   r   N)r   r   r  r  r"   rK   mutate_ad_groupscreate_ad_groupr   r   r_   r`   r]   ra   rb   rc   rd   )	rf   r6   r4   r   r   rk   rl   rN  r  s	          @@r   test_create_ad_groupz&TestCreateAdGroup.test_create_ad_group  s    %K%K	 3J##/09  -:MNj:k**7''#	 ( 
 &$''''''''z'''z''''''&'''&''''''$'''$''''''''''r   c                    t               t               fd}||j                  _        t               |j                  _        t        d      j                  _        |j                  dd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)r  c                 4    | dk(  rS | dk(  rS t               S rI  rL  rM  s    r   r   z]TestCreateAdGroup.test_create_ad_group_default_status_paused.<locals>.get_service_side_effect   rO  r   zcustomers/123/adGroups/789r   u   기본상태 광고그룹r   )r?  r   rQ  r   r   r   r  r   r  r  r   N)r   r   r  r  r"   rK   rR  rS  r   r]   r^   r_   r`   ra   rb   rc   rd   )rf   r6   r4   r   r   rh   rk   r   r   r  r  r   rN  r  s               @@r   *test_create_ad_group_default_status_pausedz<TestCreateAdGroup.test_create_ad_group_default_status_paused  s   %K%K	 3J##/09  -:MNj:k**7'', ( 
 zz/(/z(#/x/#x////#x//////v///v///z///(///#///x////////r   c                    t               }||j                  _        t               |j                  _        t	        d      |j
                  _        |j                  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}}y
)u2   delete_ad_group() 호출 시 True를 반환한다.rP  456TrV   r+  r   rQ   rT   rU   N)r   r   r"   r  rK   rR  delete_ad_groupr]   r^   r_   r`   ra   rb   rc   rd   )	rf   r6   r4   rN  r   rg   rh   ri   rj   s	            r   test_delete_ad_groupz&TestCreateAdGroup.test_delete_ad_group4  s    %K3D##009  -:MNj:k**7''.v~vvvr   c                    t               }||j                  _        t               |j                  _        t	        d      |j
                  _        |j                  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
}y
)u2   update_ad_group() 호출 시 dict를 반환한다.rP  rY  u   업데이트된 광고그룹)r   r   r   r   r   r   N)r   r   r"   r  rK   rR  update_ad_groupr   r   r_   r`   r]   ra   rb   rc   rd   )rf   r6   r4   rN  r   rk   rl   s          r   test_update_ad_groupz&TestCreateAdGroup.test_update_ad_group?  s    %K3D##009  -:MNj:k**7''4R'S&$''''''''z'''z''''''&'''&''''''$'''$''''''''''r   N)r|   r}   r~   r   rT  rW  r[  r^  r   r   r   rF  rF    s    -(402		(r   rF  c                   "    e Zd ZdZd Zd Zd Zy)TestListKeywordsu'   list_keywords: list[dict] 반환 검증c                 P   t               }||j                  _        t               }d|j                  _        d|j                  j
                  _        d|j                  j
                  j                  _        d|j                  j                  _        d|j                  _
        d|j                  _        |g|j                  _        |j                  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}d |D        }t1        |      }|sddt#        j$                         v st'        j(                  t0              rt'        j*                  t0              ndt'        j*                  |      t'        j*                  |      dz  }t-        t'        j.                  |            dx}}y)u6   list_keywords() 호출 시 list[dict]를 반환한다.i     파이썬 개발BROADr   i 5   rY  ad_group_idr   r   r   r   r   Nc              3   <   K   | ]  }t        |t                y wr   r   r   s     r   r   z6TestListKeywords.test_list_keywords.<locals>.<genexpr>d  r   r   r   r   rX   )r   r   r"   ad_group_criterioncriterion_idkeywordtext
match_typer   r   r8  r6  r   r   list_keywordsr   r   r_   r`   r]   ra   rb   rc   rd   r   r:  s	            r   test_list_keywordsz#TestListKeywords.test_list_keywordsS  s   #+3B##0k.2+.@&&+9@&&116-6%%*06-/2e+%%%%8&$''''''''z'''z''''''&'''&''''''$'''$''''''''''=f==s=========s===s==============r   c           	         t               }||j                  _        g |j                  _        |j	                  d       |j                  j
                  \  }}d}|j                  }d}d}	 |||	      }
||
v }|st        j                  d|fd||
f      t        j                  |      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}x}	}
y)u<   list_keywords()가 ad_group_id를 WHERE 절에 포함한다.rY  re  r   r   r   r   r;   r   r   r   Nr   r   r"   r   rm  r   r   r]   r^   rb   r_   r`   ra   rc   rd   r   s                 r   0test_list_keywords_includes_ad_group_id_in_queryzATestListKeywords.test_list_keywords_includes_ad_group_id_in_queryf  s    #+3B##0.0+/#**44	6/

/7/B/
7B//u/////u////u////////////
///7///B////////////r   c           	         t               }||j                  _        g |j                  _        |j	                  dd       |j                  j
                  \  }}d}|j                  }d}d}	 |||	      }
||
v }|st        j                  d|fd||
f      t        j                  |      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}x}	}
y)u<   list_keywords()에 limit 값이 GAQL 쿼리에 반영된다.rY     )rf  r   5r   r   r   r   r;   r   r   r   Nrp  r   s                 r   !test_list_keywords_limit_in_queryz2TestListKeywords.test_list_keywords_limit_in_queryq  s    #+3B##0.0+a8#**44	6-fjj--"-j"--s-----s----s------f---f---j------"------------r   N)r|   r}   r~   r   rn  rq  ru  r   r   r   r`  r`  P  s    1>&	0	.r   r`  c                   "    e Zd ZdZd Zd Zd Zy)TestAddKeywordsu&   add_keywords: list[dict] 반환 검증c                   	
 t               	t               
	
fd}||j                  _        t               |j                  _        t        dd      
j                  _        ddddddg}|j                  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}d |D        }t#        |      }|sddt        j                         v st        j                  t"              rt        j                  t"              ndt        j                  |      t        j                  |      dz  }t        t        j                   |            dx}}y)u5   add_keywords() 호출 시 list[dict]를 반환한다.c                 4    | dk(  rS | dk(  rS t               S NrK  AdGroupCriterionServicerL  r   rN  mock_criterion_svcs    r   r   zBTestAddKeywords.test_add_keywords.<locals>.get_service_side_effect  )    ''((00));r   &customers/123/adGroupCriteria/456~1001&customers/123/adGroupCriteria/456~1002rb  rc  rk  rl  u   구글 광고EXACTrY  rf  keywordsr   r   r   r   r   Nc              3   <   K   | ]  }t        |t                y wr   r   r   s     r   r   z4TestAddKeywords.test_add_keywords.<locals>.<genexpr>  r   r   r   r   rX   )r   r   r  r  r"   rK   mutate_ad_group_criteriaadd_keywordsr   r   r_   r`   r]   ra   rb   rc   rd   r   )rf   r6   r4   r   r  r   rk   rl   rh   rN  r}  s            @@r   test_add_keywordsz!TestAddKeywords.test_add_keywords  sk   %K&[	 3J##/09  -CV44D
33@ (w?$G<
 $$$J&$''''''''z'''z''''''&'''&''''''$'''$''''''''''=f==s=========s===s==============r   c                 >   t               t               fd}||j                  _        t               |j                  _        t        ddd      j                  _        ddddd	dd
ddg}|j                  d|       j                  j                          y)uO   add_keywords()는 여러 키워드를 한 번의 mutate 호출로 추가한다.c                 4    | dk(  rS | dk(  rS t               S rz  rL  r|  s    r   r   z]TestAddKeywords.test_add_keywords_calls_mutate_criteria_once.<locals>.get_service_side_effect  r~  r   r  r  z&customers/123/adGroupCriteria/456~1003u
   키워드1rc  r  u
   키워드2PHRASEu
   키워드3r  rY  r  N)	r   r   r  r  r"   rK   r  r  r   )rf   r6   r4   r   r  rN  r}  s        @@r   ,test_add_keywords_calls_mutate_criteria_oncez<TestAddKeywords.test_add_keywords_calls_mutate_criteria_once  s    %K&[	 3J##/09  -CV444D
33@ "9!:!9

 	A33FFHr   c                    t               t               fd}||j                  _        t               |j                  _        t        d      j                  _        |j                  ddddg      }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   }|
|v }|slt        j                  d|fd|
|f      t        j                  |
      t        j                  |      dz  }dd|iz  }t        t        j                   |            dx}
x}}y)u@   add_keywords() 반환 각 항목에 resource_name 키가 있다.c                 4    | dk(  rS | dk(  rS t               S rz  rL  r|  s    r   r   z`TestAddKeywords.test_add_keywords_result_contains_resource_name.<locals>.get_service_side_effect  r~  r   z&customers/123/adGroupCriteria/456~9999rY  u   단일 키워드rc  r  r  r
   r   r   r   r   r   r   r   NrF   r   r   )z%(py1)s in %(py4)s)r   rZ   r[   r\   )r   r   r  r  r"   rK   r  r  r   r]   r^   r_   r`   ra   rb   rc   rd   )rf   r6   r4   r   r   rg   r   r   rm   r   r   rk   rl   rN  r}  s                @@r   /test_add_keywords_result_contains_resource_namez?TestAddKeywords.test_add_keywords_result_contains_resource_name  sR   %K&[	 3J##/09  -CV4D
33@ $$1IJ % 

 6{a{a{ass66{a+&)+)++++)++++++)+++++++r   N)r|   r}   r~   r   r  r  r  r   r   r   rw  rw  }  s    0>:I<,r   rw  c                       e Zd ZdZd Zd Zy)TestUpdateKeywordStatusu)   update_keyword_status: dict 반환 검증c                    t               }||j                  _        t               |j                  _        t	        d      |j
                  _        |j                  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}y)u8   update_keyword_status() 호출 시 dict를 반환한다.r  1001rY  r   keyword_criterion_idrf  r   r   r   r   r   r   N)r   r   r"   r  rK   r  update_keyword_statusr   r   r_   r`   r]   ra   rb   rc   rd   )rf   r6   r4   r}  r   rk   rl   s          r   test_update_keyword_statusz2TestUpdateKeywordStatus.test_update_keyword_status  s    &[3E##009  -CV4D
33@ --!' . 
 &$''''''''z'''z''''''&'''&''''''$'''$''''''''''r   c                    t               }||j                  _        t               |j                  _        t	        d      |j
                  _        |j                  dd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}	}|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}	}|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)uS   update_keyword_status() 반환 dict에 criterion_id, ad_group_id, status가 있다.r  r  rY  r   r  ri  r   r  r   r  r  r   Nrf  r   )r   r   r"   r  rK   r  r  r   r]   r^   r_   r`   ra   rb   rc   rd   )rf   r6   r4   r}  r   rh   rk   r   r   r  r  r   s               r   1test_update_keyword_status_contains_expected_keyszITestUpdateKeywordStatus.test_update_keyword_status_contains_expected_keys  s$   &[3E##009  -CV4D
33@ --!' . 
 zz3.3z.)3V3)V3333)V333333v333v333z333.333)333V3333333zz1-1z-(1E1(E1111(E111111v111v111z111-111(111E1111111zz0(0z(#0y0#y0000#y000000v000v000z000(000#000y00000000r   N)r|   r}   r~   r   r  r  r   r   r   r  r    s    3($1r   r  c                   F    e Zd ZdZd Zd Zd Zd Zd Zd Z	d Z
d	 Zd
 Zy)TestGetInsightsuG   get_insights: list[dict] 반환 및 잘못된 entity_type 예외 검증c                 h   t               }||j                  _        t               }d|j                  _        d|j                  _        d|j                  _        d|j                  _	        d|j                  _
        d|j                  _        d|j                  _        d|j                  _        d	|j                  _        |g|j                  _        |j!                  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  }t1        t+        j2                  |            d}d |D        }t5        |      }|sddt'        j(                         v st+        j,                  t4              rt+        j.                  t4              ndt+        j.                  |      t+        j.                  |      dz  }t1        t+        j2                  |            dx}}y)u5   get_insights() 호출 시 list[dict]를 반환한다.r   u   분석 캠페인
2024-01-15i     i` 皙?p  r   r   r   entity_typer   r   r   r   r   Nc              3   <   K   | ]  }t        |t                y wr   r   r   s     r   r   z4TestGetInsights.test_get_insights.<locals>.<genexpr>  r   r   r   r   rX   )r   r   r"   r   r   r   segmentsdatemetricsimpressionsclickscost_microsctraverage_cpcconversionsr   get_insightsr   r   r_   r`   r]   ra   rb   rc   rd   r   r:  s	            r   test_get_insightsz!TestGetInsights.test_get_insights	  s   #+3B##0k.("& ")"&"$/2e+$$U
$C&$''''''''z'''z''''''&'''&''''''$'''$''''''''''=f==s=========s===s==============r   c                 \   t               }||j                  _        g |j                  _        |j	                  d       |j                  j
                  \  }}d}|j                  }d}d}	 |||	      }
|
j                  } |       }||v }|st        j                  d|fd||f      t        j                  |      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      t        j                  |	      t        j                  |
      t        j                  |      t        j                  |      dz  }d	d
|iz  }t        t        j                  |            dx}x}x}x}x}	x}
x}}y)uV   entity_type 기본값은 'campaign'이며, campaign 테이블을 FROM에 사용한다.r   r   r   r   r   )z%(py1)s in %(py15)s
{%(py15)s = %(py13)s
{%(py13)s = %(py11)s
{%(py11)s = %(py5)s
{%(py5)s = %(py3)s.get
}(%(py7)s, %(py9)s)
}.lower
}()
}r;   )r   rS   rU   r   r   r   r   py15zassert %(py17)spy17N)r   r   r"   r   r  r   r   lowerr]   r^   rb   r_   r`   ra   rc   rd   )rf   r6   r4   r   rH   r;   r   r   r   r   r   @py_assert12@py_assert14rg   @py_format16@py_format18s                   r   .test_get_insights_default_entity_type_campaignz>TestGetInsights.test_get_insights_default_entity_type_campaign  s   #+3B##0.0+E"#**44	6<VZZ<<<Z4<4::<:<<z<<<<<z<<<<z<<<<<<V<<<V<<<Z<<<<<<<<<4<<<:<<<<<<<<<<<<r   c                 
   t               }||j                  _        t               }d|j                  _        d|j                  _        d|j                  _        d|j                  _	        d|j                  _
        d|j                  _        d|j                  _        d|j                  _        d	|j                  _        |g|j                  _        |j!                  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  }t1        t+        j2                  |            d}y)uG   entity_type='ad_group'으로 광고그룹 인사이트를 조회한다.rd  u   광고그룹 인사이트r  i  2   i r  r     rY  r6  r  r   r   r   r   r   N)r   r   r"   r6  r   r   r  r  r  r  r  r  r  r  r  r   r  r   r   r_   r`   r]   ra   rb   rc   rd   r   s           r   test_get_insights_ad_group_typez/TestGetInsights.test_get_insights_ad_group_type*  s5   #+3B##0k7("&"("&"#/2e+$$U
$C&$''''''''z'''z''''''&'''&''''''$'''$''''''''''r   c           	          t               }||j                  _        g |j                  _        |j	                  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	}|j                  j                  \  }}d
}	|j                  }
d}d} |
||      }|	|v }|st        j                   d|fd|	|f      t        j                  |	      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}x}}y	)u@   entity_type='keyword'로 키워드 인사이트를 조회한다.r  rj  r  r   r   r   r   r   Nkeyword_viewr   r   r   r   r;   r   r   r   )r   r   r"   r   r  r   r   r_   r`   r]   ra   rb   rc   rd   r   r   r^   )rf   r6   r4   r   r   rk   rl   rH   r;   r   r   r   r   r   rg   r   r   s                    r   test_get_insights_keyword_typez.TestGetInsights.test_get_insights_keyword_type?  s   #+3B##0.0+$$V$C&$''''''''z'''z''''''&'''&''''''$'''$''''''''''#**44	688G8R8GR!88~!88888~!8888~888888888888888G888R888!888888888r   c                     t        j                  t              5  |j                  dd       ddd       y# 1 sw Y   yxY w)uF   지원하지 않는 entity_type 전달 시 ValueError가 발생한다.r   invalid_typer  Nrt   ru   rv   r  rf   r6   r4   s      r   %test_get_insights_invalid_entity_typez5TestGetInsights.test_get_insights_invalid_entity_typeK  s:    ]]:& 	C>B	C 	C 	Cs	   7A c                     t        j                  t        d      5  |j                  dd       ddd       y# 1 sw Y   yxY w)uB   ValueError 메시지에 잘못된 entity_type 값이 포함된다.unsupported_entityry   r   r  Nr  r  s      r   -test_get_insights_invalid_entity_type_messagez=TestGetInsights.test_get_insights_invalid_entity_type_messageP  s>    ]]:-AB 	I3GH	I 	I 	Is	   9Ac           	         t               }||j                  _        g |j                  _        |j	                  d       |j                  j
                  \  }}d}|j                  }d}d}	 |||	      }
||
v }|st        j                  d|fd||
f      t        j                  |      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}x}	}
y)uB   date_range 기본값 'LAST_7_DAYS'가 GAQL 쿼리에 포함된다.r   LAST_7_DAYSr   r   r   r   r;   r   r   r   Nr   r   r"   r   r  r   r   r]   r^   rb   r_   r`   ra   rc   rd   r   s                 r   0test_get_insights_default_date_range_last_7_daysz@TestGetInsights.test_get_insights_default_date_range_last_7_daysU  s    #+3B##0.0+E"#**44	67

777B7
7B 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77777B777 777777777r   c           	         t               }||j                  _        g |j                  _        |j	                  ddd       |j                  j
                  \  }}d}|j                  }d}d}	 |||	      }
||
v }|st        j                  d|fd||
f      t        j                  |      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}x}	}
y)u;   사용자 지정 date_range가 GAQL 쿼리에 포함된다.r   r   LAST_30_DAYS)r  
date_ranger   r   r   r   r;   r   r   r   Nr  r   s                 r   #test_get_insights_custom_date_rangez3TestGetInsights.test_get_insights_custom_date_range`  s    #+3B##0.0+EznU#**44	688G8R8GR!88~!88888~!8888~888888888888888G888R888!888888888r   c                 J   t               }||j                  _        t               }d|j                  _        d|j                  _        d|j                  _        d|j                  _	        d|j                  _
        d|j                  _        d|j                  _        d|j                  _        d	|j                  _        |g|j                  _        |j!                  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  }
t1        t%        j2                  |
            dx}x}}|d   }|j4                  }d} ||      }d
}||k(  }|st%        j&                  d|fd||f      t%        j.                  |      t%        j.                  |      t%        j.                  |      t%        j.                  |      t%        j.                  |      dz  }dd|iz  }t1        t%        j2                  |            dx}x}x}x}x}}|d   }|j4                  }d} ||      }d}||k(  }|st%        j&                  d|fd||f      t%        j.                  |      t%        j.                  |      t%        j.                  |      t%        j.                  |      t%        j.                  |      dz  }dd|iz  }t1        t%        j2                  |            dx}x}x}x}x}}y)uR   get_insights() 반환 dict 각 항목에 entity_id와 entity_type이 포함된다.i	  u   리포트 캠페인z
2024-01-20i  d   i'	 r  r  rs  777r   r  r
   r   r   r   r   r   r   r   Nr   	entity_id)zJ%(py7)s
{%(py7)s = %(py3)s
{%(py3)s = %(py1)s.get
}(%(py5)s)
} == %(py10)s)r   rS   rU   r   py10zassert %(py12)spy12r  )r   r   r"   r   r   r   r  r  r  r  r  r  r  r  r  r   r  r   r]   r^   r_   r`   ra   rb   rc   rd   r   )rf   r6   r4   r   r<   r   rg   r   r   rm   r   r   r   @py_assert9r   @py_format11@py_format13s                    r   2test_get_insights_returns_records_with_entity_infozBTestGetInsights.test_get_insights_returns_records_with_entity_infok  sS   #+3B##0k1("& "("&"#/2e+$$U
$C6{a{a{ass66{aay2y}}2[2}[)2U2)U2222)U222y222}222[222)222U22222222ay9y}}9]9}]+9z9+z9999+z999y999}999]999+999z99999999r   N)r|   r}   r~   r   r  r  r  r  r  r  r  r  r  r   r   r   r  r    s4    Q>,	=(*
9C
I
	8	9:r   r  c                   @    e Zd ZdZd Zd Zd Zd Zd Zd Z	d Z
d	 Zy
)TestCreateResponsiveSearchAdu=   create_responsive_search_ad: dict 반환 및 유효성 검증c                     t               t               fd}||j                  _        t               |j                  _        t        d      j                  _        fS )uH   create_responsive_search_ad에 필요한 서비스 mock을 설정한다.c                 4    | dk(  rS | dk(  rS t               S )NrK  AdGroupAdServicerL  )r   mock_ad_group_ad_svcrN  s    r   r   zNTestCreateResponsiveSearchAd._setup_rsa_mocks.<locals>.get_service_side_effect  s)    ''(())++;r   z customers/123/adGroupAds/456~999)r   r   r  r  r"   rK   mutate_ad_group_ads)rf   r4   r   r  rN  s      @@r   _setup_rsa_mocksz-TestCreateResponsiveSearchAd._setup_rsa_mocks  s\    %K({	 3J##/ 1:  -@STv@w00= "666r   c                    | j                  |       |j                  dg dddg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}y)u>   create_responsive_search_ad() 호출 시 dict를 반환한다.rY  )u   헤드라인1u   헤드라인2u   헤드라인3u   설명1u   설명2https://example.comrf  	headlinesdescriptions	final_urlr   r   r   r   r   N)r  create_responsive_search_adr   r   r_   r`   r]   ra   rb   rc   rd   r	  s         r    test_create_responsive_search_adz=TestCreateResponsiveSearchAd.test_create_responsive_search_ad  s    o.33I#Y/+	 4 
 &$''''''''z'''z''''''&'''&''''''$'''$''''''''''r   c                 6   | j                  |       |j                  dg dddg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}}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)uE   반환 dict에 resource_name, ad_group_id, final_url이 포함된다.rY  H1H2H3D1D2zhttps://example.com/landingr  rF   r   r  r   r  rT   rU   Nrf  r  )
r  r  r]   r^   rb   r_   r`   ra   rc   rd   r  s           r   >test_create_responsive_search_ad_result_contains_expected_keysz[TestCreateResponsiveSearchAd.test_create_responsive_search_ad_result_contains_expected_keys  sB   o.33(3	 4 
 (&((((&(((((((((&(((&(((((((&}&&&&}&&&}&&&&&&&&&&&&&&&&${f$$$${f$$${$$$$$$f$$$f$$$$$$$r   c                     | j                  |       t        j                  t        d      5  |j	                  dddgddgd	       d
d
d
       y
# 1 sw Y   y
xY w)u=   헤드라인이 3개 미만이면 ValueError가 발생한다.   헤드라인ry   rY  r  r  r  r  r  r  Nr  rt   ru   rv   r  r  s      r   9test_create_responsive_search_ad_too_few_headlines_raiseszVTestCreateResponsiveSearchAd.test_create_responsive_search_ad_too_few_headlines_raises  sZ    o.]]:^< 	..!,"D\/	 / 	 	 	   AAc           	          | j                  |       t        j                  t        d      5  |j	                  dt        d      D cg c]  }d| 	 c}ddgd	       d
d
d
       y
c c}w # 1 sw Y   y
xY w)uA   헤드라인이 15개를 초과하면 ValueError가 발생한다.r  ry   rY     Hr  r  r  r  N)r  rt   ru   rv   r  r   )rf   r6   r4   r   s       r   :test_create_responsive_search_ad_too_many_headlines_raiseszWTestCreateResponsiveSearchAd.test_create_responsive_search_ad_too_many_headlines_raises  su    o.]]:^< 	..!,1"I6qQqc76"D\/	 / 	 	 7	 	s   A-A(A-(A--A6c                     | j                  |       t        j                  t        d      5  |j	                  dg ddgd       ddd       y# 1 sw Y   yxY w)	u7   설명이 2개 미만이면 ValueError가 발생한다.   설명ry   rY  r  r  r  r  Nr  r  s      r   <test_create_responsive_search_ad_too_few_descriptions_raiseszYTestCreateResponsiveSearchAd.test_create_responsive_search_ad_too_few_descriptions_raises  sT    o.]]:X6 	..!,"V/	 / 	 	 	s   AAc                     | j                  |       t        j                  t        d      5  |j	                  dg dg dd       ddd       y# 1 sw Y   yxY w)	u:   설명이 4개를 초과하면 ValueError가 발생한다.r  ry   rY  r  )r  r  D3D4D5r  r  Nr  r  s      r   =test_create_responsive_search_ad_too_many_descriptions_raiseszZTestCreateResponsiveSearchAd.test_create_responsive_search_ad_too_many_descriptions_raises  sR    o.]]:X6 	..!,;/	 / 	 	 	r  c           	         | j                  |       |j                  dt        dd      D cg c]  }d| 	 c}t        dd      D cg c]  }d| 	 c}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}yc c}w c c}w )uM   최대 허용 개수(헤드라인 15개, 설명 4개)로 정상 생성된다.rY  r
   r  r  rs  r  zhttps://example.com/maxr  r   r   r   r   r   N)r  r  r   r   r   r_   r`   r]   ra   rb   rc   rd   )rf   r6   r4   r   r   rk   rl   s          r   ?test_create_responsive_search_ad_max_headlines_and_descriptionsz\TestCreateResponsiveSearchAd.test_create_responsive_search_ad_max_headlines_and_descriptions  s    o.3338B<@aaS)@05a<1F1#,</	 4 
 &$''''''''z'''z''''''&'''&''''''$'''$'''''''''' A<s   E>
FN)r|   r}   r~   r   r  r  r  r  r  r  r  r  r   r   r   r  r    s-    G7((%



(r   r  c                   "    e Zd ZdZd Zd Zd Zy)TestInterfaceConsistencyuA   GoogleAdsClient와 MetaAdsClient의 공통 인터페이스 검증c                 `   g d}|D cg c]  }t        t        |      r| }}| }|s~t        j                  d|       dz   ddt	        j
                         v st        j                  |      rt        j                  |      ndiz  }t        t        j                  |            d}yc c}w )uL   GoogleAdsClient가 MetaAdsClient와 공통 메서드를 모두 구현한다.r   r   r  r!  r,  r  r   uE   GoogleAdsClient에 다음 공통 메서드가 누락되었습니다: 
>assert not %(py0)srR   missingN
hasattrr%   r]   _format_assertmsgr_   r`   ra   rb   rc   rd   )rf   common_methodsmr	  rh   @py_format2s         r   test_interface_consistencyz3TestInterfaceConsistency.test_interface_consistency  sz    
 -PGOQ4O1PP{m{mmcdkclmmmmmmm7mmm7mmmmmm Q
   B+B+c           
      t   g d}|D cg c]  }t        t        t        |d            r|! }}| }|s~t        j                  d|       dz   ddt        j                         v st        j                  |      rt        j                  |      ndiz  }t        t        j                  |            d}yc c}w )u9   GoogleAdsClient의 공통 메서드들이 callable이다.r  Nu4   GoogleAdsClient에서 callable이 아닌 메서드: r  rR   not_callable)callabler:   r%   r]   r  r_   r`   ra   rb   rc   rd   )rf   r  r  r  rh   r  s         r   $test_all_common_methods_are_callablez=TestInterfaceConsistency.test_all_common_methods_are_callable  s    
 $2ea'/[\^bBc9deefff#WXdWe!fffffff<fff<ffffff fs
   B5B5c                 `   g d}|D cg c]  }t        t        |      r| }}| }|s~t        j                  d|       dz   ddt	        j
                         v st        j                  |      rt        j                  |      ndiz  }t        t        j                  |            d}yc c}w )uO   Google Ads 전용 메서드(키워드/광고그룹/RSA 관련)가 존재한다.)r9  rS  r]  rZ  rm  r  r  r  uE   GoogleAdsClient에 다음 전용 메서드가 누락되었습니다: r  rR   r	  Nr
  )rf   google_specific_methodsr  r	  rh   r  s         r   &test_google_ads_specific_methods_existz?TestInterfaceConsistency.test_google_ads_specific_methods_exist!  s|    	#
 6YW_VW=X1YY{m{mmcdkclmmmmmmm7mmm7mmmmmm Zr  N)r|   r}   r~   r   r  r  r  r   r   r   r  r    s    Kn g nr   r  __main__z-v):r   builtinsr_   _pytest.assertion.rewrite	assertionrewriter]   r   r   pathlibr   unittest.mockr   r   rt   pathinsertstr__file__parentr   r   _pkg_MockSDKClientClsr   r%   type	Exception_MockGoogleAdsExceptionr!   MessageToDict	FieldMask_patched_sdk_clsr1   r"   utils.google_ads_clientfixturer/   r4   r6   rA   rK   rM   r   r   r   r   r  r2  rF  r`  rw  r  r  r  r  r|   mainr   r   r   <module>r0     so  $  
   *  3tH~,,334 5$S $U-=-= $	 
D 
 K =N) * :3i\2F @W) * =;DRT;U) * 8:C+, - 7 

128	
4589I3<;##078 8 ? ?   ! !
) 
  !" !"R L@ @F10 10h=@ =@@07 07p,B ,B^J( J(d*. *.ZW, W,t'1 '1^z: z:Dn( n(l2n 2nj zFKK4 ! I8 8 8 8s$   =	H*H#H*H'	#H**H4