
    (<iM                     <   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mZ ddlm	Z	 ddl
mZ ddlZddlmZmZmZmZmZmZmZmZm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)zBTests for utm_builder.py - TDD: Write tests first, then implement.    N)Path)Any)patch)	ALLOWED_CAMPAIGNSALLOWED_LANDING_DOMAINSALLOWED_MEDIUMSALLOWED_SOURCES	BatchItemValidationErrorbuild_utm_urlprocess_batchvalidate_paramsc                   <    e Zd ZddZddZddZddZddZddZy)	TestBuildUtmUrlNc                 z    t        ddddd      }d|v sJ d|v sJ d	|v sJ d
|v sJ |j                  d      sJ y)uO   source, medium, campaign, content, base 모두 정상 → 완성된 URL 반환.https://incar-top.tistory.commetacpcAB_A_snucarousel_a1basesourcemediumcampaigncontentutm_source=metazutm_medium=cpczutm_campaign=AB_A_snuzutm_content=carousel_a1N)r   
startswithselfurls     I/home/jay/workspace/.worktrees/task-2057-dev2/scripts/test_utm_builder.pytest_basic_url_all_paramsz)TestBuildUtmUrl.test_basic_url_all_params    sh    0!
 !C'''3&&&&#---(C///~~=>>>    c                 ^    t        dddd      }d|v sJ d|v sJ d|v sJ d	|vsJ d
|vsJ y)u*   content, term 없이도 정상 URL 생성.r   googledisplayorg_mover   r   r   r   utm_source=googlezutm_medium=displayzutm_campaign=org_moveutm_contentutm_termNr   r   s     r"    test_url_without_optional_paramsz0TestBuildUtmUrl.test_url_without_optional_params/   s`    0	
 #c)))#s***&#---C'''$$$r$   c                 :    t        ddddd      }d|v sd|v sJ y	y	)
u$   term 파라미터 포함 URL 생성.r   naver_sar   always_Au   이직준비r   r   r   r   termz-utm_term=%EC%9D%B4%EC%A7%81%EC%A4%80%EB%B9%84	utm_term=Nr-   r   s     r"   test_url_with_termz"TestBuildUtmUrl.test_url_with_term=   s9    0
 ?#EX[I[[[I[Er$   c                 >    t        dddddd      }d|v sJ d	|v sJ y
)u   content + term 모두 포함.r   kakaor'   always_B	banner_01u   전직r   r   r   r   r   r3   zutm_content=banner_01r4   Nr-   r   s     r"   !test_url_with_all_optional_paramsz1TestBuildUtmUrl.test_url_with_all_optional_paramsH   s<    0
 '#---c!!!r$   c                 :    t        dddd      }d|v sJ d|v sJ y)	u7   긴급 랜딩페이지 (incar-top1.tistory.com) 허용.zhttps://incar-top1.tistory.comr   socialurgent_Ar)   zincar-top1.tistory.comr   Nr-   r   s     r"   test_urgent_landing_pagez(TestBuildUtmUrl.test_urgent_landing_pageU   s6    1	
 (3... C'''r$   c                 :    t        dddd      }d|v sJ d|v sJ y)	uN   base URL에 이미 쿼리스트링이 있는 경우에도 올바르게 처리.z1https://incar-top.tistory.com/entry/test?ref=homer   r   r   r)   r   zref=homeNr-   r   s     r"   !test_base_url_with_existing_queryz1TestBuildUtmUrl.test_base_url_with_existing_query`   s6    D	
 !C'''S   r$   returnN)	__name__
__module____qualname__r#   r.   r5   r;   r?   rA    r$   r"   r   r      s!    ?%	\"	(	!r$   r   c                   $    e Zd ZddZddZddZy)TestValidateSourceNc                    t        j                  t              5 }t        dddd       ddd       dt	        j
                        j                         v s(dt	        |j
                        j                         v sJ yy# 1 sw Y   WxY w)	u/   허용되지 않는 source → ValidationError.invalid_sourcer   r   r   r   r   r   r   N
utm_sourcer   pytestraisesr   r   strvaluelowerr    exc_infos     r"   test_invalid_source_raisesz-TestValidateSource.test_invalid_source_raisesp   sz    ]]?+ 	x'#4		 s8>>288::h#hnnJ]JcJcJe>eee>e:	 	s   B  B	c                     t        j                  t              5 }t        dddd       ddd       t	        j
                        t        fdt        D              sJ y# 1 sw Y   9xY w)u@   에러 메시지에 허용 값 목록이 포함되어야 한다.facebookr   r   r   rL   Nc              3   &   K   | ]  }|v  
 y wNrG   ).0s	error_msgs     r"   	<genexpr>zYTestValidateSource.test_invalid_source_message_contains_allowed_values.<locals>.<genexpr>        ;a1	>;   )rO   rP   r   r   rQ   rR   anyr	   r    rU   r]   s     @r"   3test_invalid_source_message_contains_allowed_valueszFTestValidateSource.test_invalid_source_message_contains_allowed_values{   s^    ]]?+ 	x!#4		 '	;?;;;;	 	   A##A,c                 8    t         D ]  }t        |ddd        y)u'   모든 허용 source 값 검증 통과.r   r   r   rL   N)r	   r   )r    r   s     r"   test_valid_sources_passz*TestValidateSource.test_valid_sources_pass   s%    % 	F#4		r$   rB   )rD   rE   rF   rV   rc   rf   rG   r$   r"   rI   rI   o   s    	f<r$   rI   c                   $    e Zd ZddZddZddZy)TestValidateMediumNc                     t        j                  t              5 }t        dddd       ddd       dt	        j
                        j                         v sJ y# 1 sw Y   1xY w)u/   허용되지 않는 medium → ValidationError.r   emailr   r   rL   Nr   rN   rT   s     r"   test_invalid_medium_raisesz-TestValidateMedium.test_invalid_medium_raises   s[    ]]?+ 	x#4		 3x~~.446666	 	   AA#c                     t        j                  t              5 }t        dddd       ddd       t	        j
                        t        fdt        D              sJ y# 1 sw Y   9xY w)u0   에러 메시지에 허용 medium 목록 포함.r   organicr   r   rL   Nc              3   &   K   | ]  }|v  
 y wrZ   rG   )r[   mr]   s     r"   r^   zYTestValidateMedium.test_invalid_medium_message_contains_allowed_values.<locals>.<genexpr>   r_   r`   )rO   rP   r   r   rQ   rR   ra   r   rb   s     @r"   3test_invalid_medium_message_contains_allowed_valueszFTestValidateMedium.test_invalid_medium_message_contains_allowed_values   s^    ]]?+ 	x #4		 '	;?;;;;	 	rd   c                 8    t         D ]  }t        d|dd        y)u'   모든 허용 medium 값 검증 통과.r   r   r   rL   N)r   r   )r    r   s     r"   test_valid_mediums_passz*TestValidateMedium.test_valid_mediums_pass   s%    % 	F#4		r$   rB   )rD   rE   rF   rk   rq   rs   rG   r$   r"   rh   rh      s    	7
<r$   rh   c                   $    e Zd ZddZddZddZy)TestValidateCampaignNc                     t        j                  t              5 }t        dddd       ddd       dt	        j
                        j                         v sJ y# 1 sw Y   1xY w)u1   허용되지 않는 campaign → ValidationError.r   r   random_campaignr   rL   Nr   rN   rT   s     r"   test_invalid_campaign_raisesz1TestValidateCampaign.test_invalid_campaign_raises   s[    ]]?+ 	x*4		 S0668888	 	rl   c                     t        j                  t              5 }t        dddd       ddd       t	        j
                        t        fdt        D              sJ y# 1 sw Y   9xY w)u2   에러 메시지에 허용 campaign 목록 포함.r   r   test_campaignr   rL   Nc              3   &   K   | ]  }|v  
 y wrZ   rG   )r[   cr]   s     r"   r^   z]TestValidateCampaign.test_invalid_campaign_message_contains_allowed_values.<locals>.<genexpr>   s     =a1	>=r`   )rO   rP   r   r   rQ   rR   ra   r   rb   s     @r"   5test_invalid_campaign_message_contains_allowed_valueszJTestValidateCampaign.test_invalid_campaign_message_contains_allowed_values   s_    ]]?+ 	x(4		 '	=+<====	 	rd   c                 8    t         D ]  }t        dd|d        y)u)   모든 허용 campaign 값 검증 통과.r   r   r   rL   N)r   r   )r    r   s     r"   test_valid_campaigns_passz.TestValidateCampaign.test_valid_campaigns_pass   s%    ) 	H!4		r$   rB   )rD   rE   rF   rx   r}   r   rG   r$   r"   ru   ru      s    	9
>r$   ru   c                   ,    e Zd ZddZddZddZddZy)TestValidateBaseUrlNc                 d   t        j                  t              5 }t        dddd       ddd       dt	        j
                        j                         v sNdt	        |j
                        j                         v s(d	t	        |j
                        j                         v sJ yyy# 1 sw Y   }xY w)
u2   허용되지 않는 도메인 → ValidationError.r   r   r   zhttps://evil.comrL   Ndomainr   landingrN   rT   s     r"   test_invalid_domain_raisesz.TestValidateBaseUrl.test_invalid_domain_raises   s    ]]?+ 	x#'		 HNN+1133X^^,2244C/5577	
87 5 4	 	s   B&&B/c                     t        j                  t              5 }t        dddd       ddd       t	        j
                        t        fdt        D              sJ y# 1 sw Y   9xY w)u3   에러 메시지에 허용 도메인 목록 포함.r   r   r   zhttps://not-allowed.tistory.comrL   Nc              3   &   K   | ]  }|v  
 y wrZ   rG   )r[   dr]   s     r"   r^   z[TestValidateBaseUrl.test_invalid_domain_message_contains_allowed_domains.<locals>.<genexpr>   s     Ca1	>Cr`   )rO   rP   r   r   rQ   rR   ra   r   rb   s     @r"   4test_invalid_domain_message_contains_allowed_domainszHTestValidateBaseUrl.test_invalid_domain_message_contains_allowed_domains   s_    ]]?+ 	x#6		 '	C+BCCCC	 	rd   c                 >    t         D ]  }t        dddd|         y)u)   허용된 모든 도메인 검증 통과.r   r   r   zhttps://rL   N)r   r   )r    r   s     r"   test_valid_domains_passz+TestValidateBaseUrl.test_valid_domains_pass   s,    - 	F#x(		r$   c                 ~    t        j                  t              5  t        dddd       ddd       y# 1 sw Y   yxY w)u3   URL 형식이 잘못된 경우 → ValidationError.r   r   r   z	not-a-urlrL   N)rO   rP   r   r   )r    s    r"   test_invalid_url_format_raisesz2TestValidateBaseUrl.test_invalid_url_format_raises  s7    ]]?+ 	# 		 	 	s   3<rB   )rD   rE   rF   r   r   r   r   rG   r$   r"   r   r      s    

Dr$   r   c                   ,    e Zd ZddZddZddZddZy)TestOptionalParamsNc                 0    t        ddddd      }d|vsJ y)u1   content=None → utm_content 파라미터 없음.r   r   r   r   Nr   r+   r-   r   s     r"   test_content_none_not_in_urlz/TestOptionalParams.test_content_none_not_in_url  s+    0
 C'''r$   c                 0    t        ddddd      }d|vsJ y)u+   term=None → utm_term 파라미터 없음.r   r   r   r   Nr2   r,   r-   r   s     r"   test_term_none_not_in_urlz,TestOptionalParams.test_term_none_not_in_url"  s+    0
 $$$r$   c                 0    t        ddddd      }d|vsJ y)	u/   content='' → utm_content 파라미터 없음.r   r   r   r    r   r+   Nr-   r   s     r"   $test_content_empty_string_not_in_urlz7TestOptionalParams.test_content_empty_string_not_in_url-  s+    0
 C'''r$   c                 >    t        dddddd      }d|v sJ d	|v sJ y
)u   content + term 동시 포함.r   r0   r   r1   ad_01keywordr:   zutm_content=ad_01zutm_term=keywordNr-   r   s     r"   test_term_with_contentz)TestOptionalParams.test_term_with_content8  s<    0
 #c)))!S(((r$   rB   )rD   rE   rF   r   r   r   r   rG   r$   r"   r   r     s    	(	%	()r$   r   c                   $    e Zd ZddZddZddZy)TestBatchModeNc                     ddddddddd	dd
g}t        |      }t        |      dk(  sJ |D ]  }|d   J |d   J d|d   v rJ  y)u2   배치 항목 모두 정상 → 모든 URL 생성.r   r   r   r   r   r   r   r   r   r   r&   r'   r(   rL      errorNr!   zutm_source=r   len)r    itemsresultsresults       r"   test_batch_all_validz"TestBatchMode.test_batch_all_validJ  s     !&(7 ##&7	"
  &7|q    	2F'?***%=,,, F5M111	2r$   c                     ddddddg}t        |      }t        |      dk(  sJ |d   d	   }|J d|v sJ d|v sJ d|v sJ d|v sJ y
)u:   배치 처리 결과 URL에 올바른 파라미터 포함.r7   r=   r8   img_01r   r      r   r!   Nzutm_source=kakaozutm_medium=socialzutm_campaign=always_Bzutm_content=img_01r   )r    r   r   r!   s       r"   test_batch_url_params_correctz+TestBatchMode.test_batch_url_params_correctc  s     ""&#7"
  &7|q   aj!S((("c)))&#---#s***r$   c                 (    t        g       }|g k(  sJ y)u!   빈 배치 → 빈 결과 반환.N)r   )r    r   s     r"   test_batch_empty_listz#TestBatchMode.test_batch_empty_listw  s    #"}}r$   rB   )rD   rE   rF   r   r   r   rG   r$   r"   r   r   I  s    22+(r$   r   c                       e Zd ZddZddZy)TestBatchModePartialErrorsNc                     ddddddddddddd	ddg}t        |      }t        |      d
k(  sJ |d   d   J |d   d   J |d   d   J |d   d   J |d   d   J |d   d   J y)u^   일부 항목 잘못된 경우 → 에러 항목은 error 필드, 정상 항목은 url 반환.r   r   r   r   rL   invalid_srcr&   r'   r(      r   r   Nr!   r   r   r   r    r   r   s      r"   test_batch_partial_invalidz5TestBatchModePartialErrors.test_batch_partial_invalid  s     !&7	 (&7	 ##&7	"
(  &7|q    qz'"***qz% ,,, qz'"...qz% ((( qz'"***qz% ,,,r$   c                 r    dddddg}t        |      }t        |      dk(  sJ |d   d   J |d   d
   J y	)u4   모든 항목이 잘못된 경우 → 모두 에러.
bad_source
bad_mediumbad_campaignr   rL   r   r   r   Nr!   r   r   s      r"   test_batch_all_invalidz1TestBatchModePartialErrors.test_batch_all_invalid  sd     '&*7	"
  &7|q   qz'"...qz% (((r$   rB   )rD   rE   rF   r   r   rG   r$   r"   r   r     s    #-J)r$   r   c                      e Zd Z e eej                  j                  d e ee      j                         j                  j                                    dz  dz        Zdee   dej                  e   fdZddZdd	Zdd
ZddZddZy)TestCLIWORKSPACE_ROOTscriptszutm_builder.pyargsrC   c                 j    t        j                  t        j                  | j                  g|dd      S )NT)capture_outputtext)
subprocessrunsys
executableSCRIPT)r    r   s     r"   _runzTestCLI._run  s.    ~~^^T[[040
 	
r$   Nc                 l    | j                  g d      }|j                  dk(  sJ d|j                  v sJ y)u-   정상 인자 → exit code 0, stdout에 URL.)
--sourcer   --mediumr   
--campaignr   z	--contentr   --baser   r   r   N)r   
returncodestdoutr    r   s     r"   test_cli_valid_args_exit_0z"TestCLI.test_cli_valid_args_exit_0  s=    
   A%%% FMM111r$   c                 L    | j                  g d      }|j                  dk(  sJ y)u!   잘못된 source → exit code 1.r   invalidr   r   r   r   r   r   r   N)r   r   r   s     r"   test_cli_invalid_source_exit_1z&TestCLI.test_cli_invalid_source_exit_1  s+    	
   A%%%r$   c                     | j                  g d      }|j                  |j                  z   dj                         v st	        fdt
        D              sJ yy)uD   잘못된 source → stderr에 에러 메시지 + 허용 값 목록.r   r   c              3   &   K   | ]  }|v  
 y wrZ   rG   )r[   r\   combineds     r"   r^   zATestCLI.test_cli_invalid_source_stderr_message.<locals>.<genexpr>  s     2ZQ1=2Zr`   N)r   stderrr   rS   ra   r	   )r    r   r   s     @r"   &test_cli_invalid_source_stderr_messagez.TestCLI.test_cli_invalid_source_stderr_message  sR    	
 ==6==08>>++s2Z/2Z/ZZZ/Z+r$   c                 ~   ddddddddd	dd
g}t        j                  ddd      5 }t        j                  ||       |j                  }ddd       | j                  dg      }|j                  dk(  sJ d|j                  v sJ d|j                  v sJ t        |      j                  d       y# 1 sw Y   ixY w)u*   --batch JSON 파일 → 다량 URL 출력.r   r   r   r   r   r   r&   r'   r(   rL   w.jsonFmodesuffixdeleteN--batchr   r   r*   T
missing_ok
tempfileNamedTemporaryFilejsondumpnamer   r   r   r   unlinkr    
batch_dataftmp_pathr   s        r"   test_cli_batch_modezTestCLI.test_cli_batch_mode  s     !&(7 ##&7	

 ((c'%P 	TUIIj!$vvH	 Ix01  A%%% FMM111"fmm333X.	 	s   #B33B<c                 \   ddddddddddg}t        j                  ddd	
      5 }t        j                  ||       |j                  }ddd       | j                  dg      }|j                  dk(  sJ d|j                  v sJ t        |      j                  d       y# 1 sw Y   YxY w)uL   배치 모드에서 일부 잘못된 항목 → exit code 1 (에러 있음).r   r   r   r   rL   bad_srcr   r   Fr   Nr   r   r   Tr   r   r   s        r"   &test_cli_batch_partial_error_exit_codez.TestCLI.test_cli_batch_partial_error_exit_code  s     !&7	 $&7	

 ((c'%P 	TUIIj!$vvH	 Ix01  A%%% FMM111X.	 	s   #B""B+rB   )rD   rE   rF   rQ   r   osenvironget__file__resolveparentr   listr   CompletedProcessr   r   r   r   r   r   rG   r$   r"   r   r     s    bjjnn%5s4>;Q;Q;S;Z;Z;a;a7bcdgpp  tD  D  EF
c 
z'B'B3'G 
2&& [$/8/r$   r   ) __doc__r   r   r   r   r   pathlibr   typingr   unittest.mockr   rO   utm_builderr   r   r   r	   r
   r   r   r   r   r   rI   rh   ru   r   r   r   r   r   rG   r$   r"   <module>r      s    H  	  
     
 
 
 J! J!`! !N   L   L. .h-) -)f1 1n3) 3)rt/ t/r$   