
    i,                        d Z ddlZddlZddlZddl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 ]-  Zeej                  vsej                  j!                  de       /  e       Zej$                  j'                  d e               ee      ej$                  d<    e e e       	      
      ej$                  d<   ddlZej*                  j'                  dd       ej*                  j'                  dd       ej*                  j'                  dd       ddlmZ ddlmZ defdZ G d dej6                        Zedk(  r ej<                  d       yy)u   
헤임달 (dev2-team) — 네이버 검색광고 API 클라이언트 단위 테스트
실제 API 호출 없이 unittest.mock 으로 모든 외부 의존성을 대체합니다.
    N)BytesIO)	MagicMockpatch)z/home/jay/workspacez#/home/jay/workspace/tools/naver-adsutils)load_env_keyszutils.env_loaderreturn_value)
get_loggerzutils.loggerNAVER_SEARCHAD_CUSTOMER_IDztest-customer-123NAVER_SEARCHAD_API_KEYztest-api-key-456NAVER_SEARCHAD_SECRET_KEYztest-secret-key-789)NaverSAClient)_empty_outputreturnc                     t        dt              5  t        j                  t              } t        j
                  d   | _        t        j
                  d   | _        t        j
                  d   | _        ddd       | S # 1 sw Y    S xY w)uE   load_env_keys를 mock한 상태로 NaverSAClient 를 생성합니다.zsa_api_client.load_env_keysr   r   r   N)	r   _mock_load_env_keysr   __new__osenviron_customer_id_api_key_secret_key)clients    Y/home/jay/workspace/.worktrees/task-2116-dev1/tools/naver-ads/tests/test_sa_api_client.py_make_clientr   /   st    	,.A	B E&&}5 jj)EF**%=>ZZ(CD	E
 ME
 Ms   A"A==Bc                   0    e Zd Zd Zd Zd Zd Zd Zd Zy)TestNaverSAClientc                    t               }d}d}d}|j                  |||      }| d| d| j                  d      }t        j                  |j
                  j                  d      |t        j                        j                         }t        j                  |      j                  d      }| j                  ||d       t        j                  |      }	| j                  t        |	      dd       y	)
uV   _generate_signature 가 올바른 HMAC-SHA256/base64 서명을 반환해야 합니다.1712800000000GET/ncc/campaigns.utf-8u0   HMAC-SHA256 서명이 기대값과 다릅니다.    u7   SHA-256 다이제스트는 32바이트여야 합니다.N)r   _generate_signatureencodehmacnewr   hashlibsha256digestbase64	b64encodedecodeassertEqual	b64decodelen)
selfr   	timestampmethoduriresultmessageexpected_digestexpecteddecodeds
             r   test_generate_signaturez)TestNaverSAClient.test_generate_signatureA   s    #	++IvsC Kq#/66w?((%%g.NN
 &(	 	
 ##O4;;GD+]^""6*Wr+de    c                 l   t               }|j                  dd      }g d}|D ]  }| j                  ||d| d        | j                  |d   |j                         | j                  |d   |j
                         | j                  d|d	          | j                  |d
   j                         d       y)uG   _build_headers 가 필수 헤더 5개를 모두 포함해야 합니다.r    r!   )X-Timestamp	X-API-KEY
X-CustomerzX-SignatureContent-Typeu   필수 헤더 'u   ' 가 누락되었습니다.r?   r@   zapplication/jsonrA   r>   u1   X-Timestamp 가 숫자 문자열이 아닙니다.N)r   _build_headersassertInr/   r   r   
assertTrueisdigit)r2   r   headersrequired_keyskeys        r   test_build_headersz$TestNaverSAClient.test_build_headers\   s    ''/?@a  	]CMM#w/#>Z([\	] 	-v?.0C0CD('.*AB.668:mnr<   c                     t               }ddddddg}t        j                  |d|      5 }|j                         }ddd       j	                  d	d
       | j                  |d       y# 1 sw Y   /xY w)uM   get_campaigns 가 GET /ncc/campaigns 로 _request 를 호출해야 합니다.cam-001u
   캠페인A)nccCampaignIdnamezcam-002u
   캠페인B_requestr   Nr    r!   u;   get_campaigns 반환값이 _request 결과와 다릅니다.)r   r   objectget_campaignsassert_called_once_withr/   )r2   r   mock_resultmock_reqr6   s        r   test_get_campaignsz$TestNaverSAClient.test_get_campaignso   s     (>'>

 \\&*;G 	,8))+F	, 	((0@A.kl		, 	,s   A,,A5c                    t               }g d}ddd}dg}i dfd	}t        j                  |d|	      5  |j                  |||
       ddd       | j	                  d       | j                  d   t        j                  |      d       | j	                  d       | j                  d   t        j                  |      d       y# 1 sw Y   xY w)uR   get_stats 가 fields 와 time_range 를 JSON string 으로 변환해야 합니다.)impCntclkCntsalesAmt
2026-04-01
2026-04-07)sinceuntilrK   Nc                 2    j                  |xs i        g S N)update)_method_uriparamscaptured_paramss      r   fake_requestz=TestNaverSAClient.test_get_stats_params.<locals>.fake_request   s    ""6<R0Ir<   rN   side_effect)idsfields
time_rangerh   u;   fields 가 JSON string 으로 변환되지 않았습니다.	timeRangeu?   time_range 가 JSON string 으로 변환되지 않았습니다.r^   )r   r   rO   	get_statsrC   r/   jsondumps)r2   r   rh   ri   rg   rd   rc   s         @r   test_get_stats_paramsz'TestNaverSAClient.test_get_stats_params   s    1+lC
k	 \\&*,G 	LV
K	L 	h0H%JJvI	
 	k?3K(JJz"M	
	L 	Ls   CCc           	      |   t               }t        j                  ddi      j                  d      }t	               t	              _        t	        d      _        |j                  _        t        j                  j                  dddt	               t        d	      
      ddfd	}t        d|      5  t        d      5 }|j                  dd      }ddd       ddd       | j                  dd       j!                  d       | j                  ddid       y# 1 sw Y   KxY w# 1 sw Y   OxY w)uN   _request 가 429 HTTPError 를 받으면 재시도 후 성공해야 합니다.okTr#   r   Fz,https://api.searchad.naver.com/ncc/campaignsi  zToo Many Requestss   rate limited)urlcodemsghdrsfpr   c                      dz  dk(  rS )N    )_reqtimeout
call_counthttp_429mock_success_resps     r   mock_urlopenzATestNaverSAClient.test_request_retry_on_429.<locals>.mock_urlopen   s    !OJQ$$r<   zurllib.request.urlopenre   z
time.sleepr    r!   N   uQ   urlopen 이 정확히 2번 호출되어야 합니다 (1번 실패 + 1번 성공).rw   u9   재시도 후 올바른 응답을 반환해야 합니다.)   )r   rl   rm   r&   r   	__enter____exit__readr	   urlliberror	HTTPErrorr   r   rN   r/   rQ   )	r2   r   success_bodyr~   
mock_sleepr6   r{   r|   r}   s	         @@@r   test_request_retry_on_429z+TestNaverSAClient.test_request_retry_on_429   s'    zz4,/66w?%K&/=N&O#%.E%B".:+ <<))>#' * 
 
	% +F 	>< 	>$.__U,<=F	> 	> 	Q({|**1-$/jk	> 	> 	> 	>s$   =D2	D&D2&D/	+D22D;c                 r   d}d}t        ||      }g d}|D ]  }| j                  ||d| d        |d   }| j                  d|       | j                  d|       d	D ]c  }d
D ]"  }| j                  ||   |   dd| d| d       $ | j                  ||   d   dd| d       | j                  ||   d   dd| d       e | j                  |d   g d       | j                  t        |d         dd       | j                  d|d          | j                  d|d          | j                  d|d          y)uG   _empty_output 이 필수 키와 빈 summary 를 포함해야 합니다.rY   rZ   )last_collectedsummarykeyword_groupsdaily_trendbudget	campaignsu   필수 키 'u%   ' 가 _empty_output 에 없습니다.r   today	yesterday)r   r   )impressionsclickscostconversionsr   zsummary.r"   u    가 0이 아닙니다.ctrg        u   .ctr 가 0.0이 아닙니다.cpcu   .cpc 가 0이 아닙니다.r   u,   campaigns 가 빈 리스트여야 합니다.r      u/   daily_trend 항목 수가 7이어야 합니다.dailyr   spent_today	burn_rateN)r   rC   r/   r1   )	r2   r[   r\   outputrG   rH   r   periodmetrics	            r   test_empty_output_gracefulz,TestNaverSAClient.test_empty_output_graceful   s   ue, n  	bCMM#vcU:_'`a	b #gw'k7+ - 	hFJ   FOF+vhax/FG WV_U3SHVHLi:jkWV_U3Q(6(Je8fg	h 	,b2`a 	VM23Q8ij 	gvh/0mVH%56k6(#34r<   N)	__name__
__module____qualname__r;   rI   rT   rn   r   r   rx   r<   r   r   r   <   s&    
f6o&m$
H#lP%5r<   r   __main__r   )	verbosity)__doc__r,   r)   r'   rl   sysunittesturllib.errorr   ior   unittest.mockr   r   _ppathinsertr   modules
setdefaultr   r   sa_api_clientr   collect_sa_statsr   r   TestCaser   r   mainrx   r<   r   <module>r      s3       
    *
 I B	2  k    w	 ,"+:M"N '9)+3VWN  
 

  24G H 

  .0B C 

  13H I
 ( *m v5)) v5x zHMMA r<   