
    (<ij                        U d Z ddlZddlmZ ddlmZ ddlmZ ddlm	Z	 ddl
Z
ej                  j                  d e ee      j                  j                               dZee   ed<   	 ddlZedu Ze
j*                  j-                  ed	      Z G d
 d      Z G d d      Z G d d      Z G d d      Z G d d      Z G d d      Z G d d      Z G d d      Z G d d      Z y# e$ r Y w xY w)u   
TDD RED 단계: test_crawl_utils.py
crawl_utils.py 모듈에 대한 테스트 스위트
(crawl_utils.py는 아직 구현되지 않음 - TDD RED 단계)
    N)Path)
ModuleType)Optional)	MagicMock_crawl_utilsuC   crawl_utils.py 미구현 (TDD RED 단계 - 토르가 구현 예정))reasonc                   F    e Zd ZdZedd       Zedd       Zedd       Zy)TestProxyRotatorRoundRobinu-   ProxyRotator의 round_robin 전략 테스트.Nc                     t         J g d}t        j                  |d      }|j                         dk(  sJ |j                         dk(  sJ |j                         dk(  sJ y)uD   round_robin 전략은 프록시를 순서대로 반환해야 한다.Nhttp://proxy1:8080http://proxy2:8080http://proxy3:8080round_robinstrategyr   r   r   r   ProxyRotatorget_nextselfproxiesrotators      O/home/jay/workspace/.worktrees/task-2057-dev2/scripts/tests/test_crawl_utils.py)test_round_robin_returns_proxies_in_orderzDTestProxyRotatorRoundRobin.test_round_robin_returns_proxies_in_order(   so     '''T++GmL!%9999!%9999!%9999    c                     t         J ddg}t        j                  |d      }|j                          |j                          |j                         dk(  sJ y)uK   round_robin은 마지막 프록시 이후 처음으로 순환해야 한다.Nr   r   r   r   r   r   s      r   test_round_robin_wraps_aroundz8TestProxyRotatorRoundRobin.test_round_robin_wraps_around3   s]     '''')=>++GmL!%9999r   c                     t         J t        j                  dgd      }|j                         dk(  sJ |j                         dk(  sJ y)uQ   프록시가 1개일 때 round_robin은 계속 같은 값을 반환해야 한다.Nzhttp://only-proxy:8080r   r   r   r   r   s     r   #test_round_robin_single_proxy_loopsz>TestProxyRotatorRoundRobin.test_round_robin_single_proxy_loops>   sW     '''++-E,FQ^_!%====!%====r   returnN)__name__
__module____qualname____doc___skip_if_missingr   r   r!    r   r   r
   r
   %   s?    7: : : : > >r   r
   c                   4    e Zd ZdZedd       Zedd       Zy)TestProxyRotatorRandomu(   ProxyRotator의 random 전략 테스트.Nc                     t         J g d}t        j                  |d      }t        d      D ]  }|j                         }||v rJ  y)uK   random 전략은 항상 프록시 목록 중 하나를 반환해야 한다.Nr   randomr      )r   r   ranger   )r   r   r   _results        r   *test_random_strategy_returns_value_in_listzATestProxyRotatorRandom.test_random_strategy_returns_value_in_listP   sZ     '''

 ++GhGr 	%A%%'FW$$$	%r   c                     t         J ddg}t        j                  |d      }t        d      D ch c]  }|j                          }}|j	                  t        |            sJ yc c}w )uH   random 전략 호출 결과가 항상 프록시 풀에 속해야 한다.Nz
http://a:1z
http://b:2r-   r   2   )r   r   r/   r   issubsetset)r   r   r   r0   resultss        r   1test_random_strategy_multiple_calls_are_from_poolzHTestProxyRotatorRandom.test_random_strategy_multiple_calls_are_from_pool_   si     '''.++GhG/4Ry9!7##%99G--- :s   A'r"   )r$   r%   r&   r'   r(   r2   r8   r)   r   r   r+   r+   M   s+    2% % . .r   r+   c                   F    e Zd ZdZedd       Zedd       Zedd       Zy)TestProxyRotatorEmptyListu6   ProxyRotator 빈 리스트 엣지 케이스 테스트.Nc                 b    t         J t        j                  g       }|j                         J y)uH   빈 프록시 리스트에서 get_next()는 None을 반환해야 한다.Nr   r    s     r   %test_empty_list_get_next_returns_nonez?TestProxyRotatorEmptyList.test_empty_list_get_next_returns_noner   s5     '''++B/!)))r   c                 ^    t         J t        j                  g       }t        |      dk(  sJ y)u6   빈 프록시 리스트의 len()은 0이어야 한다.Nr   r   r   lenr    s     r   test_empty_list_len_is_zeroz5TestProxyRotatorEmptyList.test_empty_list_len_is_zeroz   s2     '''++B/7|q   r   c                 f    t         J t        j                  g d      }|j                         J y)uG   random 전략에서도 빈 리스트이면 None을 반환해야 한다.Nr-   r   r   r    s     r   ,test_empty_list_random_strategy_returns_nonezFTestProxyRotatorEmptyList.test_empty_list_random_strategy_returns_none   s7     '''++BB!)))r   r"   )r$   r%   r&   r'   r(   r<   r@   rB   r)   r   r   r:   r:   o   s?    @* * ! ! * *r   r:   c                   j    e Zd ZdZedd       Zedd       Zedd       Zedd       Zedd       Z	y)	TestProxyRotatorRemoveAndLenu-   ProxyRotator remove()와 __len__() 테스트.Nc                     t         J g d}t        j                  |      }t        |      dk(  sJ |j                  d       t        |      dk(  sJ y)u,   remove() 후 len()이 1 감소해야 한다.Nr      r      )r   r   r?   remover   s      r   test_remove_decreases_lenz6TestProxyRotatorRemoveAndLen.test_remove_decreases_len   sV     '''T++G47|q   +,7|q   r   c                     t         J ddg}t        j                  |d      }|j                  d       t        d      D ]  }|j	                         dk7  rJ  y)uO   remove()된 프록시는 이후 get_next()에서 반환되지 않아야 한다.Nr   r   r   r   
   )r   r   rH   r/   r   )r   r   r   r0   s       r   "test_remove_excluded_from_rotationz?TestProxyRotatorRemoveAndLen.test_remove_excluded_from_rotation   se     '''')=>++GmL+,r 	>A##%)====	>r   c                     t         J dg}t        j                  |      }|j                  d       |j                         J t	        |      dk(  sJ y)uK   모든 프록시를 제거하면 get_next()는 None을 반환해야 한다.Nr   r   )r   r   rH   r   r?   r   s      r   !test_remove_all_proxies_then_nonez>TestProxyRotatorRemoveAndLen.test_remove_all_proxies_then_none   s\     ''''(++G4+,!)))7|q   r   c                     t         J t        j                  dg      }|j                  d       t        |      dk(  sJ y)uO   존재하지 않는 프록시 remove()는 예외 없이 처리되어야 한다.Nr   zhttp://nonexistent:9999   )r   r   rH   r?   r    s     r   %test_remove_nonexistent_proxy_is_safezBTestProxyRotatorRemoveAndLen.test_remove_nonexistent_proxy_is_safe   sE     '''++-A,BC 	017|q   r   c                 f    t         J g d}t        j                  |      }t        |      dk(  sJ y)uB   len()이 초기 프록시 리스트 개수와 일치해야 한다.N)zhttp://p1:1zhttp://p2:2zhttp://p3:3zhttp://p4:4   r>   r   s      r   test_len_reflects_initial_countz<TestProxyRotatorRemoveAndLen.test_len_reflects_initial_count   s7     '''N++G47|q   r   r"   )
r$   r%   r&   r'   r(   rI   rL   rN   rQ   rT   r)   r   r   rD   rD      sg    7! ! 	> 	> 	! 	! ! ! ! !r   rD   c                       e Zd ZdZed
d       Zed
d       Zed
d       Zed
d       Zed
d       Z	ed
d       Z
ed
d	       Zy)TestIsProxyErroru"   is_proxy_error() 함수 테스트.Nc                 \    t         J t        d      }t        j                  |      du sJ y)u/   ConnectionError는 True를 반환해야 한다.NzConnection refusedT)r   ConnectionErroris_proxy_errorr   errs     r   "test_connection_error_returns_truez3TestIsProxyError.test_connection_error_returns_true   s4     '''23**3/4777r   c                 \    t         J t        d      }t        j                  |      du sJ y)u,   TimeoutError는 True를 반환해야 한다.NzRequest timed outT)r   TimeoutErrorrY   rZ   s     r   test_timeout_error_returns_truez0TestIsProxyError.test_timeout_error_returns_true   s4     '''./**3/4777r   c                 \    t         J t        d      }t        j                  |      du sJ y)u2   일반 ValueError는 False를 반환해야 한다.NzInvalid valueF)r   
ValueErrorrY   rZ   s     r   test_value_error_returns_falsez/TestIsProxyError.test_value_error_returns_false   s3     ''')**3/5888r   c                 \    t         J t        d      }t        j                  |      du sJ y)u1   일반 Exception은 False를 반환해야 한다.NzSome generic errorF)r   	ExceptionrY   rZ   s     r   $test_generic_exception_returns_falsez5TestIsProxyError.test_generic_exception_returns_false   s4     ''',-**3/5888r   c                 \    t         J t        d      }t        j                  |      du sJ y)uN   OSError(ConnectionError의 부모)는 프록시 오류로 판별해야 한다.NzNetwork unreachableT)r   OSErrorrY   rZ   s     r   test_os_error_returns_truez+TestIsProxyError.test_os_error_returns_true   s4     '''+,**3/4777r   c                     t         J t        d      }t               }d|_        t	        |d|       t        j
                  |      du sJ y)uA   404 상태를 가진 HTTP 오류는 False를 반환해야 한다.Nz404 Not Foundi  responseFr   rd   r   status_codesetattrrY   r   http_err	mock_resps      r   !test_http_error_404_returns_falsez2TestIsProxyError.test_http_error_404_returns_false   sN     '''_-K	 #	*i0**84===r   c                     t         J t        d      }t               }d|_        t	        |d|       t        j
                  |      du sJ y)uS   500 서버 오류는 프록시 오류가 아니므로 False를 반환해야 한다.Nz500 Internal Server Errori  rj   Frk   rn   s      r   !test_http_error_500_returns_falsez2TestIsProxyError.test_http_error_500_returns_false  sO     '''89K	 #	*i0**84===r   r"   )r$   r%   r&   r'   r(   r\   r_   rb   re   rh   rq   rs   r)   r   r   rV   rV      s    ,8 8 8 8 9 9 9 9 8 8 > > > >r   rV   c                   j    e Zd ZdZedd       Zedd       Zedd       Zedd       Zedd       Z	y)	TestFetchWithRetryu$   fetch_with_retry() 함수 테스트.Nc                     t         J t               }d|_        t               }||j                  _        t        |      }t        j
                  dd|      }||k(  sJ |j                  j                  dk(  sJ y)uE   첫 번째 시도에서 성공하면 Response를 반환해야 한다.N   return_valuehttps://example.comrF   max_retriesfetcher_classrP   )r   r   rl   fetchry   fetch_with_retry
call_countr   mock_responsemock_fetcher_instanceMockFetcherClassr1   s        r   test_success_on_first_attemptz0TestFetchWithRetry.test_success_on_first_attempt  s     '''!$'! )3@##0$2GH..!*
 &&&$**55:::r   c                    t         J t               }d|_        t               }t        d      |g|j                  _        t        |      }t        j                  dd|      }||k(  sJ |j                  j                  dk(  sJ y)	uO   연결 오류 후 재시도에서 성공하면 Response를 반환해야 한다.Nrw   z
proxy downrx   rz   rF   r{   rG   )r   r   rl   rX   r~   side_effectr   r   r   s        r   .test_retries_on_connection_error_then_succeedszATestFetchWithRetry.test_retries_on_connection_error_then_succeeds/  s     '''!$'! )L)3
##/
 %2GH..!*
 &&&$**55:::r   c                 6   t         J t               }t        d      |j                  _        t        |      }t        j                  t              5  t        j                  dd|       ddd       |j                  j                  dk(  sJ y# 1 sw Y   %xY w)uC   모든 재시도 실패 시 마지막 예외를 raise해야 한다.Nzalways failsrx   rz   rF   r{   )	r   r   rX   r~   r   pytestraisesr   r   )r   r   r   s      r   1test_raises_last_exception_after_all_retries_failzDTestFetchWithRetry.test_raises_last_exception_after_all_retries_failG  s     ''' )2A.2Q##/$2GH]]?+ 	))%.	 %**55:::	 	s   BBc                 8   t         J t               }d|_        t               }t        d      |g|j                  _        t        |      }t               }d|j                  _        t        j                  dd||       |j                  j                  d	k\  sJ y)
u@   재시도 시 ProxyRotator.get_next()가 호출되어야 한다.Nrw   zfirst proxy downrx   zhttp://new-proxy:8080rz   rF   r|   proxy_rotatorr}   rP   )
r   r   rl   rX   r~   r   r   ry   r   r   )r   r   r   r   mock_rotators        r    test_proxy_rotator_used_on_retryz3TestFetchWithRetry.test_proxy_rotator_used_on_retryY  s     '''!$'! )./3
##/
 %2GH {-D*%%!&*		
 $$//1444r   c                 :   t         J t               }d|_        t               }t        d      |g|j                  _        t        |      }t               }d}||j                  _        t        j                  dd||       |j                  j                          y)	uU   프록시 오류 발생 시 해당 프록시가 rotator에서 제거되어야 한다.Nrw   zproxy errorrx   zhttp://bad-proxy:8080rz   rF   r   )r   r   rl   rX   r~   r   r   ry   r   rH   assert_called)r   r   r   r   r   	bad_proxys         r   !test_proxy_removed_on_proxy_errorz4TestFetchWithRetry.test_proxy_removed_on_proxy_errort  s     '''!$'! )M*3
##/
 %2GH {+	-6*%%!&*		
 	))+r   r"   )
r$   r%   r&   r'   r(   r   r   r   r   r   r)   r   r   ru   ru     sg    .; ;( ; ;. ; ;" 5 54 , ,r   ru   c                       e Zd ZdZedd       Zedd       Zedd       Zedd       Zedd       Z	edd       Z
edd	       Zedd
       Zy)TestGetResourceBlockTypesu,   get_resource_block_types() 함수 테스트.Nc                     t         J t        j                  d      }t        |t              sJ d|v sJ d|v sJ d|v sJ d|v sJ d|v sJ y)uR   default 프리셋은 image, font, media, stylesheet, other를 포함해야 한다.Ndefaultimagefontmedia
stylesheetotherr   get_resource_block_types
isinstancer6   r   r1   s     r   *test_default_preset_returns_expected_typeszDTestGetResourceBlockTypes.test_default_preset_returns_expected_types  sv     '''66yA&#&&&&   &   v%%%&   r   c                 J    t         J t        j                  d      }d|vsJ y)uC   default 프리셋에는 websocket이 포함되지 않아야 한다.Nr   	websocketr   r   r   s     r   &test_default_preset_excludes_websocketz@TestGetResourceBlockTypes.test_default_preset_excludes_websocket  s.     '''66yA&(((r   c                 b    t         J t        j                  d      }d|v sJ d|v sJ d|v sJ y)u?   minimal 프리셋은 image, font, media만 포함해야 한다.Nminimalr   r   r   r   r   s     r   *test_minimal_preset_returns_expected_typeszDTestGetResourceBlockTypes.test_minimal_preset_returns_expected_types  sJ     '''66yA&   &   r   c                 J    t         J t        j                  d      }d|vsJ y)uD   minimal 프리셋에는 stylesheet가 포함되지 않아야 한다.Nr   r   r   r   s     r   'test_minimal_preset_excludes_stylesheetzATestGetResourceBlockTypes.test_minimal_preset_excludes_stylesheet  s.     '''66yA6)))r   c                     t         J t        j                  d      }t        |t              sJ d|v sJ d|v sJ d|v sJ d|v sJ d|v sJ d|v sJ d	|v sJ y)
u[   aggressive 프리셋은 websocket, manifest를 포함한 모든 타입을 가져야 한다.N
aggressiver   r   r   r   r   r   manifestr   r   s     r   )test_aggressive_preset_includes_all_typeszCTestGetResourceBlockTypes.test_aggressive_preset_includes_all_types  s     '''66|D&#&&&&   &   v%%%&   f$$$V###r   c                     t         J t        j                  d      }t        j                  d      }t        |      t        |      kD  sJ y)uO   aggressive 프리셋은 default보다 더 많은 타입을 포함해야 한다.Nr   r   )r   r   r?   )r   r   r   s      r   +test_aggressive_has_more_types_than_defaultzETestGetResourceBlockTypes.test_aggressive_has_more_types_than_default  sF     '''!::<H
77	B:W---r   c                 t    t         J t        j                         }t        j                  d      }||k(  sJ y)uX   인자 없이 호출하면 default 프리셋과 동일한 결과를 반환해야 한다.Nr   r   )r   result_no_argresult_defaults      r   test_no_arg_uses_default_presetz9TestGetResourceBlockTypes.test_no_arg_uses_default_preset  s<     '''$==?%>>yI...r   c                     t         J dD ]1  }t        j                  |      }t        |t              r)J | d        y)u2   반환값은 반드시 set 타입이어야 한다.N)r   r   r   u,    프리셋의 반환 타입이 set이 아님r   )r   presetr1   s      r   test_returns_set_typez/TestGetResourceBlockTypes.test_returns_set_type  sN     ''': 	dF!::6BFfc*cvh6b,cc*	dr   r"   )r$   r%   r&   r'   r(   r   r   r   r   r   r   r   r   r)   r   r   r   r     s    6
! 
! ) ) ! ! * * $ $ . . / / d dr   r   c                       e Zd ZdZedd       Zedd       Zedd       Zedd       Zedd       Z	edd       Z
edd	       Zedd
       Zedd       Zedd       Zy)TestHtmlToMarkdownu$   html_to_markdown() 함수 테스트.Nc                 Z    t         J d}t        j                  |      }d|v sJ d|v sJ y)u<   h1 태그는 Markdown # 제목으로 변환되어야 한다.Nu   <h1>제목</h1>u   제목#r   html_to_markdownr   htmlr1   s      r   test_basic_heading_conversionz0TestHtmlToMarkdown.test_basic_heading_conversion  s?     ''' ..t46!!!f}}r   c                 N    t         J d}t        j                  |      }d|v sJ y)u:   p 태그의 텍스트는 결과에 포함되어야 한다.Nu!   <p>본문 텍스트입니다.</p>u   본문 텍스트입니다.r   r   s      r   test_paragraph_conversionz,TestHtmlToMarkdown.test_paragraph_conversion  s3     '''2..t4+v555r   c                 Z    t         J d}t        j                  |      }d|v sJ d|v sJ y)u@   a 태그는 Markdown 링크 형식으로 변환되어야 한다.N(   <a href="https://example.com">링크</a>u   링크rz   r   r   s      r   test_anchor_tag_conversionz-TestHtmlToMarkdown.test_anchor_tag_conversion  sA     '''9..t46!!!$...r   c                 ^    t         J d}t        j                  |d      }d|vsJ d|v sJ y)uE   remove_noise=True 시 script 태그 내용이 제거되어야 한다.Nu+   <p>본문</p><script>alert('xss');</script>Tremove_noisealertu   본문r   r   s      r   %test_remove_noise_removes_script_tagsz8TestHtmlToMarkdown.test_remove_noise_removes_script_tags  sC     '''<..t$Gf$$$6!!!r   c                 ^    t         J d}t        j                  |d      }d|vsJ d|v sJ y)uD   remove_noise=True 시 style 태그 내용이 제거되어야 한다.Nu0   <p>내용</p><style>.cls { color: red; }</style>Tr   z
color: redu   내용r   r   s      r   $test_remove_noise_removes_style_tagsz7TestHtmlToMarkdown.test_remove_noise_removes_style_tags  sC     '''A..t$G6)))6!!!r   c                 R    t         J d}t        j                  |d      }d|vsJ y)uG   remove_noise=True 시 noscript 태그 내용이 제거되어야 한다.Nu.   <p>텍스트</p><noscript>JS 필요</noscript>Tr   u	   JS 필요r   r   s      r   'test_remove_noise_removes_noscript_tagsz:TestHtmlToMarkdown.test_remove_noise_removes_noscript_tags(  s5     '''?..t$G&(((r   c                 ^    t         J d}t        j                  |d      }d|vsJ d|vsJ y)uB   remove_noise=True 시 svg 태그 내용이 제거되어야 한다.Nu+   <p>텍스트</p><svg><path d='M0 0'/></svg>Tr   <svgz<pathr   r   s      r   "test_remove_noise_removes_svg_tagsz5TestHtmlToMarkdown.test_remove_noise_removes_svg_tags2  sC     '''<..t$GV###f$$$r   c                 R    t         J d}t        j                  |d      }d|v sJ y)uD   remove_noise=False 시 script 태그 내용이 유지될 수 있다.Nu(   <p>본문</p><script>var x = 1;</script>Fr   z	var x = 1r   r   s      r   $test_remove_noise_false_keeps_scriptz7TestHtmlToMarkdown.test_remove_noise_false_keeps_script=  s5     '''9..t%Hf$$$r   c                 N    t         J d}t        j                  |      }d|vsJ y)uI   결과에 연속된 빈 줄이 3개 이상 이어지지 않아야 한다.Nz<p>A</p><p>B</p><p>C</p>z


r   r   s      r   test_no_excessive_blank_linesz0TestHtmlToMarkdown.test_no_excessive_blank_linesG  s5     ''')..t4 v%%%r   c                 b    t         J t        j                  d      }t        |t              sJ yu(   반환값은 str 타입이어야 한다.Nz<p>hello</p>)r   r   r   strr   s     r   test_returns_stringz&TestHtmlToMarkdown.test_returns_stringR  s0     '''..~>&#&&&r   r"   )r$   r%   r&   r'   r(   r   r   r   r   r   r   r   r   r   r   r)   r   r   r   r     s    .  6 6 / / " " " " ) ) % % % % & & ' 'r   r   c                       e Zd ZdZedd       Zedd       Zedd       Zedd       Zedd       Z	edd       Z
edd	       Zedd
       Zedd       Zedd       Zy)TestCleanHtmlu   clean_html() 함수 테스트.Nc                 Z    t         J d}t        j                  |      }d|vsJ d|vsJ y)u6   script 태그와 그 내용이 제거되어야 한다.Nu%   <p>본문</p><script>evil();</script>z<scriptzevil()r   
clean_htmlr   s      r   test_removes_script_tagsz&TestCleanHtml.test_removes_script_tagsc  sA     '''6((.&&&v%%%r   c                 Z    t         J d}t        j                  |      }d|vsJ d|vsJ y)u5   style 태그와 그 내용이 제거되어야 한다.Nu/   <p>내용</p><style>body { margin: 0; }</style>z<stylez	margin: 0r   r   s      r   test_removes_style_tagsz%TestCleanHtml.test_removes_style_tagsn  sA     '''@((.v%%%&(((r   c                 Z    t         J d}t        j                  |      }d|vsJ d|vsJ y)u8   noscript 태그와 그 내용이 제거되어야 한다.Nu=   <div><noscript>Please enable JS</noscript><p>본문</p></div>z	<noscriptzPlease enable JSr   r   s      r   test_removes_noscript_tagsz(TestCleanHtml.test_removes_noscript_tagsy  sA     '''N((.&(((!///r   c                 N    t         J d}t        j                  |      }d|vsJ y)u3   svg 태그와 그 내용이 제거되어야 한다.Nu6   <div><svg><circle r='10'/></svg><p>텍스트</p></div>r   r   r   s      r   test_removes_svg_tagsz#TestCleanHtml.test_removes_svg_tags  s3     '''G((.V###r   c                 Z    t         J d}t        j                  |      }d|vsJ d|v sJ y)u)   onclick 속성이 제거되어야 한다.Nz.<button onclick="doSomething()">Click</button>onclickClickr   r   s      r   test_removes_onclick_attributez,TestCleanHtml.test_removes_onclick_attribute  sA     '''?((.&&&&   r   c                 Z    t         J d}t        j                  |      }d|vsJ d|v sJ y)u1   style 인라인 속성이 제거되어야 한다.Nu5   <p style="color: red; font-size: 14px;">텍스트</p>zstyle="u	   텍스트r   r   s      r   test_removes_style_attributez*TestCleanHtml.test_removes_style_attribute  sA     '''F((.&&&f$$$r   c                 N    t         J d}t        j                  |      }d|v sJ y)u=   태그 제거 후 텍스트 내용은 보존되어야 한다.Nu,   <div><p>중요한 내용입니다.</p></div>u   중요한 내용입니다.r   r   s      r   test_preserves_text_contentz)TestCleanHtml.test_preserves_text_content  s3     '''=((.+v555r   c                 Z    t         J d}t        j                  |      }d|v sJ d|v sJ y)u-   href 속성은 제거되지 않아야 한다.Nr   hrefrz   r   r   s      r   test_preserves_href_attributez+TestCleanHtml.test_preserves_href_attribute  sA     '''9((.$...r   c                 b    t         J t        j                  d      }t        |t              sJ yr   r   r   r   r   r   s     r   r   z!TestCleanHtml.test_returns_string  s0     '''((8&#&&&r   c                 b    t         J t        j                  d      }t        |t              sJ y)u.   빈 HTML 입력도 str을 반환해야 한다.N r   r   s     r   test_empty_html_returns_stringz,TestCleanHtml.test_empty_html_returns_string  s0     '''((,&#&&&r   r"   )r$   r%   r&   r'   r(   r   r   r   r   r   r   r   r   r   r   r)   r   r   r   r   `  s    (& & ) ) 0 0 $ $ ! ! % % 6 6 / / ' ' ' 'r   r   )!r'   syspathlibr   typesr   typingr   unittest.mockr   r   pathinsertr   __file__parentr   __annotations__crawl_utilsModuleNotFoundError_MISSINGmarkskipifr(   r
   r+   r:   rD   rV   ru   r   r   r   r)   r   r   <module>r     s       #  3tH~,,334 5 &*hz" )	& 4;;%%P &   >  >P. .D* *B8! 8!@?> ?>Nw, w,~Qd Qdri' i'bg' g'U  		s   +C+ +C32C3