
    Ri{              	          U 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
mZ ddlZ ee      j                         j                  j                  j                  Zej"                  j%                  d eedz               dZee
   ed<   	 ej,                  j/                  d eedz  dz              ZeBej2                  6ej,                  j5                  e      Zej2                  j7                  e       ddlZddlZddl Z!dd	lm"Z"m#Z#m$Z$m%Z%m&Z&m'Z' dd
lm(Z( ddl m)Z)m*Z* dZ+dZ,dZ-dZ.dZ/	 	 d#dedede0de
fdZ1de
de
de2e
   fdZ3 G d d      Z4 G d d      Z5 G d d      Z6 G d d       Z7 G d! d"      Z8y# e$ r Y w xY w)$ui  
통합 테스트: test_crawl_integration.py

browser.py + crawl_utils + insurance_crawler + insurance_spider 모듈 간
통합 호환성을 검증하는 테스트 스위트.

주의:
- 외부 네트워크 호출 금지 (모든 테스트는 로컬 HTML fixture 사용).
- 합법적 공개 데이터(보험사 공시 페이지 등)만을 대상으로 합니다.
    N)Path)AnyOptionalscripts_browser_modbrowserz
browser.py)ProxyRotator
clean_htmlfetch_with_retryget_resource_block_typeshtml_to_markdownis_proxy_errorInsuranceCrawler)InsuranceSpiderResponseHistoryu  
<html>
<body>
  <div class="product">
    <span class="name">화재보험</span>
    <span class="price">50000</span>
  </div>
  <div class="product">
    <span class="name">자동차보험</span>
    <span class="price">100000</span>
  </div>
  <div class="product">
    <span class="name">생명보험</span>
    <span class="price">30000</span>
  </div>
  <a class="next-page" href="/products?page=2">다음</a>
</body>
</html>
u   
<html>
<body>
  <table>
    <tr><th>보험명</th><th>보험료</th><th>가입기간</th></tr>
    <tr><td>화재보험</td><td>50000</td><td>1년</td></tr>
    <tr><td>자동차보험</td><td>100000</td><td>1년</td></tr>
  </table>
</body>
</html>
u   
<html>
<body>
  <script>alert('xss');</script>
  <p>보험 공시 정보</p>
  <div class="product"><span class="name">화재보험</span></div>
</body>
</html>
u.   <html><body><p>보험 정보</p></body></html> htmlurlstatusreturnc                 D    ddl m}  || |d      }||_        i |_        |S )uG   Selector를 Response 대용으로 사용 (Response는 Selector 상속).r   )SelectorF)r   adaptive)scrapling.parserr   r   meta)r   r   r   r   sels        U/home/jay/workspace/.worktrees/task-2117-dev1/scripts/tests/test_crawl_integration.py_make_mock_responser   j   s(     *
4S5
1CCJCHJ    spiderresponsec                 p   K   g }| j                  |      2 3 d{   }|j                  |       7 6 |S w)u9   parse() async generator의 결과를 리스트로 수집.N)parseappend)r!   r"   resultsitems       r   _collect_parse_resultsr(   x   s=     Gll8,  dt,Ns   6313636c                       e Zd ZdZddZddZddZddZddZddZ	dd	Z
dd
ZddZddZddZddZddZddZddZddZddZddZy)TestImportCompatibilityu@   모든 모듈이 충돌 없이 import 가능한지 검증한다.Nc                     t         J d       y)u<   browser.py가 importlib으로 정상 로드되어야 한다.Nu+   browser.py를 로드하지 못했습니다.)r   selfs    r   test_browser_module_loadedz2TestImportCompatibility.test_browser_module_loaded   s    'V)VV'r    c                 8    t         J t        t         d      sJ y)u9   browser.py에 STEALTH_ARGS 변수가 존재해야 한다.NSTEALTH_ARGSr   hasattrr,   s    r    test_browser_stealth_args_existsz8TestImportCompatibility.test_browser_stealth_args_exists       '''|^444r    c                 8    t         J t        t         d      sJ y)u9   browser.py에 HARMFUL_ARGS 변수가 존재해야 한다.NHARMFUL_ARGSr1   r,   s    r    test_browser_harmful_args_existsz8TestImportCompatibility.test_browser_harmful_args_exists   r4   r    c                 J    t         J t        t         j                        sJ y)uE   browser.py의 generate_stealth_headers가 호출 가능해야 한다.N)r   callablegenerate_stealth_headersr,   s    r   .test_browser_generate_stealth_headers_callablezFTestImportCompatibility.test_browser_generate_stealth_headers_callable   s"    '''==>>>r    c                 J    t         J t        t         j                        sJ y)u?   browser.py의 get_google_referer가 호출 가능해야 한다.N)r   r9   get_google_refererr,   s    r   (test_browser_get_google_referer_callablez@TestImportCompatibility.test_browser_get_google_referer_callable   s"    '''77888r    c                 J    t         J t        t         j                        sJ y)uD   browser.py의 create_resource_blocker가 호출 가능해야 한다.N)r   r9   create_resource_blockerr,   s    r   -test_browser_create_resource_blocker_callablezETestImportCompatibility.test_browser_create_resource_blocker_callable   s"    '''<<===r    c                 8    t         J t        t         d      sJ y)uC   browser.py에 BLOCKED_RESOURCE_TYPES 변수가 존재해야 한다.NBLOCKED_RESOURCE_TYPESr1   r,   s    r   *test_browser_blocked_resource_types_existszBTestImportCompatibility.test_browser_blocked_resource_types_exists   s    '''|%=>>>r    c                     t         J y)u7   crawl_utils.ProxyRotator가 import 가능해야 한다.N)r	   r,   s    r   )test_crawl_utils_proxy_rotator_importablezATestImportCompatibility.test_crawl_utils_proxy_rotator_importable   s    '''r    c                 &    t        t              sJ y)u9   crawl_utils.is_proxy_error가 import 가능해야 한다.N)r9   r   r,   s    r   *test_crawl_utils_is_proxy_error_importablezBTestImportCompatibility.test_crawl_utils_is_proxy_error_importable   s    '''r    c                 &    t        t              sJ y)u;   crawl_utils.fetch_with_retry가 import 가능해야 한다.N)r9   r   r,   s    r   ,test_crawl_utils_fetch_with_retry_importablezDTestImportCompatibility.test_crawl_utils_fetch_with_retry_importable       ()))r    c                 &    t        t              sJ y)uC   crawl_utils.get_resource_block_types가 import 가능해야 한다.N)r9   r   r,   s    r   4test_crawl_utils_get_resource_block_types_importablezLTestImportCompatibility.test_crawl_utils_get_resource_block_types_importable   s    0111r    c                 &    t        t              sJ y)u;   crawl_utils.html_to_markdown가 import 가능해야 한다.N)r9   r   r,   s    r   ,test_crawl_utils_html_to_markdown_importablezDTestImportCompatibility.test_crawl_utils_html_to_markdown_importable   rK   r    c                 &    t        t              sJ y)u5   crawl_utils.clean_html이 import 가능해야 한다.N)r9   r
   r,   s    r   &test_crawl_utils_clean_html_importablez>TestImportCompatibility.test_crawl_utils_clean_html_importable   s    
###r    c                     t         J y)uA   insurance_crawler.InsuranceCrawler가 import 가능해야 한다.Nr   r,   s    r   !test_insurance_crawler_importablez9TestImportCompatibility.test_insurance_crawler_importable   s    +++r    c                     t         J y)u?   insurance_spider.InsuranceSpider가 import 가능해야 한다.N)r   r,   s    r    test_insurance_spider_importablez8TestImportCompatibility.test_insurance_spider_importable       ***r    c                     t         J y)u?   insurance_spider.ResponseHistory가 import 가능해야 한다.N)r   r,   s    r    test_response_history_importablez8TestImportCompatibility.test_response_history_importable   rV   r    c                 P    t         j                  }t        j                  }||u sJ y)uM   crawl_utils와 insurance_crawler 사이에 이름 충돌이 없어야 한다.N)crawl_utilsr	   _insurance_crawler_mod)r-   cu_ProxyRotatoric_ProxyRotators      r   %test_no_name_conflict_between_modulesz=TestImportCompatibility.test_no_name_conflict_between_modules   s(     &220==/111r    c                 X    t         t        usJ t         t        usJ t        t        usJ y)u7   각 모듈은 서로 다른 모듈 객체여야 한다.N)r[   _insurance_spider_modrZ   r,   s    r   !test_all_modules_distinct_objectsz9TestImportCompatibility.test_all_modules_distinct_objects   s-    %-BBBB%[888$K777r    r   N)__name__
__module____qualname____doc__r.   r3   r7   r;   r>   rA   rD   rF   rH   rJ   rM   rO   rQ   rS   rU   rX   r^   ra    r    r   r*   r*      s`    JW5
5
?
9
>
?
((*2*$,++28r    r*   c                   p    e Zd ZdZddZddZddZddZddZddZ	dd	Z
dd
ZddZddZddZddZy)TestInterfaceConsistencyuV   함수 인터페이스가 모듈 간에 일관성 있게 연결되는지 검증한다.Nc                 T    t         J t        t         j                  t              sJ y)u4   browser.py의 STEALTH_ARGS는 tuple이어야 한다.N)r   
isinstancer0   tupler,   s    r   test_stealth_args_is_tuplez3TestInterfaceConsistency.test_stealth_args_is_tuple   $    ''',33U;;;r    c                 T    t         J t        t         j                  t              sJ y)u4   browser.py의 HARMFUL_ARGS는 tuple이어야 한다.N)r   rk   r6   rl   r,   s    r   test_harmful_args_is_tuplez3TestInterfaceConsistency.test_harmful_args_is_tuple   rn   r    c                 T    t         J t        t         j                  t              sJ y)u<   browser.py의 BLOCKED_RESOURCE_TYPES는 set이어야 한다.N)r   rk   rC   setr,   s    r   "test_blocked_resource_types_is_setz;TestInterfaceConsistency.test_blocked_resource_types_is_set   s$    ''',==sCCCr    c                 x    ddg}t        |      }|j                  J t        |j                  t              sJ y)u`   InsuranceCrawler가 proxy_list로 생성할 때 crawl_utils.ProxyRotator를 사용해야 한다.zhttp://proxy1:8080zhttp://proxy2:8080
proxy_listN)r   proxy_rotatorrk   r	   )r-   proxiescrawlers      r   :test_insurance_crawler_uses_proxy_rotator_from_crawl_utilszSTestInterfaceConsistency.test_insurance_crawler_uses_proxy_rotator_from_crawl_utils   s?    ')=>"g6$$000'//>>>r    c                 z    t               }d}|j                  |      }t        |t              sJ d|v sJ d|vsJ y)ua   InsuranceCrawler.to_llm_input()은 clean_html→html_to_markdown 파이프라인을 사용한다.u3   <p>보험 공시 정보</p><script>evil();</script>u   보험 공시 정보evil()N)r   to_llm_inputrk   str)r-   ry   r   results       r   ?test_insurance_crawler_clean_html_and_html_to_markdown_pipelinezXTestInterfaceConsistency.test_insurance_crawler_clean_html_and_html_to_markdown_pipeline   sK    "$D%%d+&#&&&%///v%%%r    c                 r    t        dg      }t        |d      sJ t        |j                  t              sJ y)uV   InsuranceSpider 인스턴스에 _crawler 속성(InsuranceCrawler)이 있어야 한다.https://example.com
start_urls_crawlerNr   r2   rk   r   r   r-   r!   s     r   +test_insurance_spider_has_crawler_attributezDTestInterfaceConsistency.test_insurance_spider_has_crawler_attribute  5     -B,CDvz***&//+;<<<r    c                 r    t        dg      }t        |d      sJ t        |j                  t              sJ y)u]   InsuranceSpider 인스턴스에 response_history 속성(ResponseHistory)이 있어야 한다.r   r   response_historyN)r   r2   rk   r   r   r   s     r   4test_insurance_spider_has_response_history_attributezMTestInterfaceConsistency.test_insurance_spider_has_response_history_attribute
  s7     -B,CDv1222&11?CCCr    c                     t               }|j                  dddgddi       |j                         }t        |      dk(  sJ y)	uW   ResponseHistory.record()는 url, status, redirects, headers 인자를 받아야 한다.r      zhttps://old.example.comzContent-Typez	text/html)	redirectsheaders   N)r   recordget_historylenr-   historyr   s      r   -test_response_history_record_method_signaturezFTestInterfaceConsistency.test_response_history_record_method_signature  sQ    !#!01#[1	 	 	
 $$&6{ar    c                 x   t               }|j                  dd       t        j                         5 }t	        t        |      dz        }|j                  |       t        |      j                         sJ t        |      dz  }|j                  |       |j                         sJ 	 ddd       y# 1 sw Y   yxY w)uE   ResponseHistory.save()는 str 또는 Path 인자를 받아야 한다.r   r   zhistory_str.jsonzhistory_path.jsonN)r   r   tempfileTemporaryDirectoryr~   r   saveexists)r-   r   tmpdirpath_strpath_objs        r   $test_response_history_save_signaturez=TestInterfaceConsistency.test_response_history_save_signature  s    !#,c2((* 	%f4<*<<=HLL">((***F|&99HLL"??$$$	% 	% 	%s   A5B00B9c                 X    t         J t        d      }t         j                  }||k(  sJ y)uf   crawl_utils.get_resource_block_types('default')와 browser.BLOCKED_RESOURCE_TYPES가 같아야 한다.Ndefault)r   r   rC   )r-   default_typesbrowser_typess      r   <test_get_resource_block_types_matches_blocked_resource_typeszUTestInterfaceConsistency.test_get_resource_block_types_matches_blocked_resource_types+  s1    '''0;"."E"E---r    c                     ddddid}t        dg|      }t        t        d      }t        j                  t        ||            }|D cg c]  }t        |t              s| }}t        |      d	k\  sJ y
c c}w )ub   InsuranceSpider.parse()에서 css 모드는 _crawler.extract_with_selector를 호출해야 한다.css.productname.namemodecss_selectorfieldsr   r   extraction_configr   r   N	r   r   HTML_PRODUCTSasynciorunr(   rk   dictr   r-   configr!   r"   r&   ritemss          r   6test_insurance_spider_extract_with_selector_delegationzOTestInterfaceConsistency.test_insurance_spider_extract_with_selector_delegation2  s     &w'

 !-.$
 '}:OP++4VXFG#;qz!T':;;5zQ <   A8!A8c                     ddd}t        dg|      }t        t        d      }t        j                  t        ||            }|D cg c]  }t        |t              s| }}t        |      dk\  sJ yc c}w )u\   InsuranceSpider.parse()에서 table 모드는 _crawler.extract_table을 호출해야 한다.tabler   table_selectorr   r   r   r   N	r   r   
HTML_TABLEr   r   r(   rk   r   r   r   s          r   .test_insurance_spider_extract_table_delegationzGTestInterfaceConsistency.test_insurance_spider_extract_table_delegationB  su    !W= -.$
 'z7LM++4VXFG#;qz!T':;;5zQ <s   A5A5rb   )rc   rd   re   rf   rm   rp   rs   rz   r   r   r   r   r   r   r   r   rg   r    r   ri   ri      sC    `<
<
D
?&=D %. 
r    ri   c                   H    e Zd ZdZd
dZd
dZd
dZd
dZd
dZd
dZ	d
d	Z
y)TestDataFlowIntegrationuS   외부 네트워크 없이 HTML에서 데이터까지의 플로우를 검증한다.Nc                    t        d      }|j                  t              }|j                  |dddd      }t	        |t
              sJ t        |      dk(  sJ t	        |d	   t              sJ |d	   d
   dk(  sJ |d	   d   dk(  sJ y)u^   HTML → parse() → extract_with_selector() → 결과 dict 플로우가 동작해야 한다.Fr   r   r   .pricer   pricer   r      r   r      화재보험r   50000N)r   r$   r   extract_with_selectorrk   listr   r   r-   ry   pager   s       r   2test_html_parse_extract_with_selector_returns_dictzJTestDataFlowIntegration.test_html_parse_extract_with_selector_returns_dictW  s    "E2}}]+..##h7 / 

 &$'''6{a&)T***ay N222ay!W,,,r    c                 
   t        d      }|j                  t              }|j                  |      }t	        |t
              sJ t        |      dk(  sJ t	        |d   t              sJ |d   d   dk(  sJ |d   d   dk(  sJ y	)
uV   HTML → parse() → extract_table() → 결과 dict 플로우가 동작해야 한다.Fr      r   	   보험명r   u	   보험료r   N)r   r$   r   extract_tablerk   r   r   r   r   s       r   *test_html_parse_extract_table_returns_dictzBTestDataFlowIntegration.test_html_parse_extract_table_returns_dictf  s    "E2}}Z(&&t,&$'''6{a&)T***ay%777ay%000r    c                     t               }|j                  t              }t        |t              sJ t        |      dkD  sJ d|v sd|v sJ yy)uP   HTML → to_llm_input() → markdown 문자열 플로우가 동작해야 한다.r   r   u   보험N)r   r}   r   rk   r~   r   r-   ry   r   s      r   .test_html_to_llm_input_returns_markdown_stringzFTestDataFlowIntegration.test_html_to_llm_input_returns_markdown_stringq  sU    "$%%m4&#&&&6{Q'8v+===+='r    c                    t               }|j                  dddg       |j                  dd       |j                         }t        |      dk(  sJ |j	                  d      }t        |      dk\  sJ |d	   d
   dk(  sJ |j	                  d      }t        |      dk\  sJ t        j                         5 }t        |      dz  }|j                  |       |j                         sJ t        j                  |j                  d            }t        |t              sJ t        |      dk(  sJ 	 ddd       y# 1 sw Y   yxY w)uP   record → get_history → get_chain → save(tmpfile) → 파일 존재 확인.zhttps://example.com/finalr   zhttps://example.com/old)r   zhttps://example.com/otheri-  r   r   r   r   zhistory.jsonutf-8encodingN)r   r   r   r   	get_chainr   r   r   r   r   jsonloads	read_textrk   r   )r-   r   all_histchain_final	chain_oldr   pathdatas           r   7test_response_history_record_get_history_get_chain_savezOTestDataFlowIntegration.test_response_history_record_get_history_get_chain_savez  s;   !#'01 	 	

 	2C8&&(8}!!!''(CD;1$$$1~e$(CCCC%%&?@	9~"""((* 	"f<.0DLL;;= =::dnngn>?DdD)))t9>!>	" 	" 	"s   5A9D88Ec                 t    d}t        |      }d|vsJ t        |d      }t        |t              sJ d|v sJ y)uR   clean_html() → html_to_markdown() 파이프라인이 정상 동작해야 한다.u?   <p>보험 정보</p><script>evil();</script><style>.a{}</style>r|   F)remove_noiseu   보험 정보N)r
   r   rk   r~   )r-   raw_htmlcleanedmarkdowns       r   .test_clean_html_then_html_to_markdown_pipelinezFTestDataFlowIntegration.test_clean_html_then_html_to_markdown_pipeline  sI    TX&w&&&#G%@(C((((***r    c                     ddg}t        |      }|j                  J |j                  j                         }|j                  j                         }|dk(  sJ |dk(  sJ y)u[   InsuranceCrawler에 proxy_list 전달 시 ProxyRotator가 올바르게 동작해야 한다.zhttp://p1:8080zhttp://p2:8080ru   N)r   rw   get_next)r-   rx   ry   firstseconds        r   *test_proxy_rotator_round_trip_with_crawlerzBTestDataFlowIntegration.test_proxy_rotator_round_trip_with_crawler  so    #%56"g6$$000%%..0&&//1(((())))r    c                 >   dddddd}t        dg|      }t        t        d	
      }t        j                  t        ||            }|D cg c]  }t        |t              s| }}t        |      dk\  sJ t        d |D              sJ |d   d   d	k(  sJ yc c}w )uH   Spider parse CSS 모드 전체 플로우: HTML → parse → dict items.r   r   r   r   r   r   r   r   zhttps://example.com/productsr   r   c              3   $   K   | ]  }d |v  
 yw)_source_urlNrg   ).0r'   s     r   	<genexpr>zOTestDataFlowIntegration.test_spider_parse_css_mode_full_flow.<locals>.<genexpr>  s     ;T=D(;s   r   r   N)
r   r   r   r   r   r(   rk   r   r   allr   s          r   $test_spider_parse_css_mode_full_flowz<TestDataFlowIntegration.test_spider_parse_css_mode_full_flow  s     &&:

 !-.$
 '}:XY++4VXFG#;qz!T':;;5zQ;U;;;;Qx&*HHHH	 <s   B"Brb   )rc   rd   re   rf   r   r   r   r   r   r   r   rg   r    r   r   r   T  s)    ]-	1>":+*Ir    r   c                   P    e Zd ZdZddZddZddZddZddZddZ	dd	Z
dd
Zy)TestSpiderCrawlerIntegrationu>   InsuranceSpider와 InsuranceCrawler의 연동을 검증한다.Nc                 r    t        dg      }t        |d      sJ t        |j                  t              sJ y)uP   InsuranceSpider 초기화 시 InsuranceCrawler가 내부 생성되어야 한다.r   r   r   Nr   r   s     r   5test_spider_init_creates_insurance_crawler_internallyzRTestSpiderCrawlerIntegration.test_spider_init_creates_insurance_crawler_internally  r   r    c                 R    t        dg      }|j                  j                  du sJ y)uR   InsuranceSpider 내부의 _crawler는 adaptive=False로 초기화되어야 한다.r   r   FN)r   r   r   r   s     r   "test_spider_crawler_adaptive_falsez?TestSpiderCrawlerIntegration.test_spider_crawler_adaptive_false  s(     -B,CD''5000r    c                 L   ddddid}t        dg|      }|j                  J |j                  d	   dk(  sJ t        t              }t	        j
                  t        ||            }|D cg c]  }t        |t              s| }}t        |      d
k\  sJ d|d   v sJ yc c}w )ud   extraction_config mode=css 전달 시 parse()에서 extract_with_selector가 선택되어야 한다.r   r   r   r   r   r   r   Nr   r   r   )
r   r   r   r   r   r   r(   rk   r   r   r   s          r   Dtest_spider_extraction_config_css_mode_selects_extract_with_selectorzaTestSpiderCrawlerIntegration.test_spider_extraction_config_css_mode_selects_extract_with_selector  s     &w'

 !-.$
 ''333''/5888&}5++4VXFG#;qz!T':;;5zQq!!! <s   +B!B!c                    ddd}t        dg|      }t        t              }t        j                  t        ||            }|D cg c]  }t        |t              s| }}t        |      dk\  sJ d|d   v s
d|d   v sJ y	y	c c}w )
u^   extraction_config mode=table 전달 시 parse()에서 extract_table이 선택되어야 한다.r   r   r   r   r   r   r   col_0Nr   r   s          r   >test_spider_extraction_config_table_mode_selects_extract_tablez[TestSpiderCrawlerIntegration.test_spider_extraction_config_table_mode_selects_extract_table  s    !W= -.$
 'z2++4VXFG#;qz!T':;;5zQeAh&'U1X*===*=& <s   BBc                     ddd}t        dg|      }t        t              }t        j                  t        ||            }t        |t              sJ y)ub   extraction_config mode=similar 전달 시 parse()에서 extract_similar가 선택되어야 한다.similarr   )r   reference_selectorr   r   N)r   r   r   r   r   r(   rk   r   )r-   r   r!   r"   r&   s        r   *test_spider_extraction_config_similar_modezGTestSpiderCrawlerIntegration.test_spider_extraction_config_similar_mode  sX     ",
 !-.$
 '}5++4VXFG'4(((r    c                    ddddid}t        dg|      }t        t        d	      }t        j                  t        ||             |j                  j                         }t        |      d
k\  sJ t        d |D              sJ y)uD   parse() 호출 시 ResponseHistory에 URL이 기록되어야 한다.r   r   r   r   r   r   r   https://example.com/insurancer   r   c              3   ,   K   | ]  }|d    dk(    yw)r   r  Nrg   )r   es     r   r   z]TestSpiderCrawlerIntegration.test_spider_response_history_records_on_parse.<locals>.<genexpr>  s     P11U8>>Ps   N)
r   r   r   r   r   r(   r   r   r   any)r-   r   r!   r"   r   s        r   -test_spider_response_history_records_on_parsezJTestSpiderCrawlerIntegration.test_spider_response_history_records_on_parse   s    PWGXY -.$
 '}:YZ*68<=))5577|q   PPPPPr    c                    t        j                         5 }t        |      dz  }t        dgt	        |            }t        j                  |j                  d             |j                         sJ 	 ddd       y# 1 sw Y   yxY w)u;   on_start() 실행 후 output_dir이 생성되어야 한다.
spider_outr   r   
output_dirFresumingN)	r   r   r   r   r~   r   r   on_startr   )r-   r   r  r!   s       r   'test_spider_on_start_creates_output_dirzDTestSpiderCrawlerIntegration.test_spider_on_start_creates_output_dir  su    ((* 	'ff4J$12z?F KK78$$&&&	' 	' 	's   AA<<Bc                 |   t        j                         5 }t        dg|      }|j                  j	                  dd       t        j                  |j                  d             t        j                  |j                                t        |      dz  }|j                         sJ 	 ddd       y# 1 sw Y   yxY w)u[   on_close() 실행 후 response_history.json 파일이 output_dir에 저장되어야 한다.r   r  r   Fr  zresponse_history.jsonN)r   r   r   r   r   r   r   r  on_closer   r   )r-   r   r!   history_files       r   'test_spider_on_close_saves_history_filezDTestSpiderCrawlerIntegration.test_spider_on_close_saves_history_file  s    ((* 		)f$12!F ##**+@#FKK78KK)*<*AAL&&(((		) 		) 		)s   BB22B;rb   )rc   rd   re   rf   r   r  r  r  r
  r  r  r  rg   r    r   r   r     s.    H=1
"&>)Q	')r    r   c                       e Zd ZdZddZddZddZddZddZddZ	dd	Z
dd
ZddZddZddZddZddZddZddZddZy)TestEdgeCasesu^   경계 조건 및 예외 상황에서 각 모듈이 안전하게 동작하는지 검증한다.Nc                     t        d      }|j                  t              }|j                  |dddi      }|g k(  sJ y)uO   빈 HTML에서 extract_with_selector()는 빈 리스트를 반환해야 한다.Fr   r   r   r   r   N)r   r$   
HTML_EMPTYr   r   s       r   8test_empty_html_extract_with_selector_returns_empty_listzFTestEdgeCases.test_empty_html_extract_with_selector_returns_empty_list.  sK    "E2}}Z(..#G$ / 

 ||r    c                 v    t        d      }|j                  t              }|j                  |      }|g k(  sJ y)uG   빈 HTML에서 extract_table()은 빈 리스트를 반환해야 한다.Fr   N)r   r$   r   r   r   s       r   0test_empty_html_extract_table_returns_empty_listz>TestEdgeCases.test_empty_html_extract_table_returns_empty_list9  s6    "E2}}Z(&&t,||r    c                     t               }|j                  t              }t        |t              sJ |j                         dk(  sJ y)uF   빈 HTML에서 to_llm_input()은 빈 문자열을 반환해야 한다.r   N)r   r}   r   rk   r~   stripr   s      r   1test_empty_html_to_llm_input_returns_empty_stringz?TestEdgeCases.test_empty_html_to_llm_input_returns_empty_string@  s>    "$%%j1&#&&&||~###r    c                 8    t        d      }|j                  J y)uM   proxy_list=None이면 InsuranceCrawler.proxy_rotator는 None이어야 한다.Nru   )r   rw   r-   ry   s     r   2test_none_proxy_list_crawler_proxy_rotator_is_nonez@TestEdgeCases.test_none_proxy_list_crawler_proxy_rotator_is_noneG  s    "d3$$,,,r    c                 <    t        d      }|j                  du sJ y)uE   adaptive=False이면 InsuranceCrawler.adaptive가 False여야 한다.Fr   N)r   r   r(  s     r   +test_adaptive_false_disables_smart_matchingz9TestEdgeCases.test_adaptive_false_disables_smart_matchingL  s     "E25(((r    c                     ddddid}t        dg|      }t        t        d      }t        j                  t        ||            }|D cg c]  }t        |t              s| }}t        |      d	k(  sJ y
c c}w )uK   빈 HTML에서 Spider.parse()는 아이템을 yield하지 않아야 한다.r   r   r   r   r   r   r   r   r   N)	r   r   r   r   r   r(   rk   r   r   r   s          r   -test_empty_html_spider_parse_returns_no_itemsz;TestEdgeCases.test_empty_html_spider_parse_returns_no_itemsQ  s}    PWGXY -.$
 'z7LM++4VXFG#;qz!T':;;5zQ <r   c                     t        d      }|j                  t              }|j                  |dddi      }|g k(  sJ y)um   존재하지 않는 CSS 셀렉터로 extract_with_selector() 호출 시 빈 리스트를 반환해야 한다.Fr   z.does-not-existr   r   r   N)r   r$   r   r   r   s       r   0test_nonexistent_css_selector_returns_empty_listz>TestEdgeCases.test_nonexistent_css_selector_returns_empty_list]  sK    "E2}}]+..*G$ / 

 ||r    c                 v    t        d      }|j                  t              }|j                  |      }|g k(  sJ y)uW   테이블이 없는 HTML에서 extract_table()은 빈 리스트를 반환해야 한다.Fr   N)r   r$   HTML_SIMPLEr   r   s       r    test_no_table_returns_empty_listz.TestEdgeCases.test_no_table_returns_empty_listh  s6    "E2}}[)&&t,||r    c                 H    t               }|j                  d      }|g k(  sJ y)uN   빈 ResponseHistory에서 get_chain()은 빈 리스트를 반환해야 한다.r   N)r   r   r   s      r   3test_response_history_empty_get_chain_returns_emptyzATestEdgeCases.test_response_history_empty_get_chain_returns_emptyo  s'    !#""#89||r    c                 j   t               }|j                  dd       |j                          t        j                         5 }t        |      dz  }|j                  |       |j                         sJ t        j                  |j                  d            }|g k(  sJ 	 ddd       y# 1 sw Y   yxY w)uN   clear() 후 save()하면 빈 배열([]) JSON 파일이 생성되어야 한다.r   r   z
empty.jsonr   r   N)r   r   clearr   r   r   r   r   r   r   r   )r-   r   r   r   r   s        r   0test_response_history_clear_then_save_empty_filez>TestEdgeCases.test_response_history_clear_then_save_empty_fileu  s    !#,c2((* 	f<,.DLL;;= =::dnngn>?D2::	 	 	s   AB))B2c                 `    t         J t         j                         }t        |t              sJ y)uB   browser.generate_stealth_headers()는 dict를 반환해야 한다.N)r   r:   rk   r   r-   r   s     r   *test_generate_stealth_headers_returns_dictz8TestEdgeCases.test_generate_stealth_headers_returns_dict  s,    '''668&$'''r    c                 l    t         J t         j                         }t        |t              sJ d|v sJ y)uL   browser.get_google_referer()는 Google URL 문자열을 반환해야 한다.Nz
google.com)r   r=   rk   r~   r9  s     r   *test_get_google_referer_returns_google_urlz8TestEdgeCases.test_get_google_referer_returns_google_url  s:    '''002&#&&&v%%%r    c                 V    t         J t         j                         }t        |      sJ y)uT   browser.create_resource_blocker()는 호출 가능한 함수를 반환해야 한다.N)r   r@   r9   )r-   handlers     r   -test_create_resource_blocker_returns_callablez;TestEdgeCases.test_create_resource_blocker_returns_callable  s*    '''668   r    c                 z    t        d      }|j                  t              }|j                  |d      }|g k(  sJ y)ul   존재하지 않는 reference_selector로 extract_similar() 호출 시 빈 리스트를 반환해야 한다.Fr   z.nonexistent-ref)r	  N)r   r$   r   extract_similarr   s       r   8test_extract_similar_nonexistent_reference_returns_emptyzFTestEdgeCases.test_extract_similar_nonexistent_reference_returns_empty  s<    "E2}}]+((BT(U||r    c                 >    t        g       }|j                         J y)uT   빈 proxy_list로 ProxyRotator 생성 시 get_next()는 None을 반환해야 한다.N)r	   r   )r-   rotators     r   *test_proxy_rotator_empty_list_returns_nonez8TestEdgeCases.test_proxy_rotator_empty_list_returns_none  s!    r"!)))r    c                     t        dgd      }t        t              }t        j                  t        ||            }|D cg c]  }t        |t              s| }}t        |      dk(  sJ yc c}w )uR   extraction_config=None이면 parse()는 아이템을 yield하지 않아야 한다.r   Nr   r   r   )r-   r!   r"   r&   r   r   s         r   1test_spider_extraction_config_none_yields_nothingz?TestEdgeCases.test_spider_extraction_config_none_yields_nothing  sh     -."
 '}5++4VXFG#;qz!T':;;5zQ <s   A.A.rb   )rc   rd   re   rf   r!  r#  r&  r)  r+  r-  r/  r2  r4  r7  r:  r<  r?  rB  rE  rG  rg   r    r   r  r  +  sU    h	$-
)

	
(&!*
	r    r  )r   r   )9rf   r   importlib.util	importlibr   sysr   pathlibr   typingr   r   pytest__file__resolveparent
_WORKSPACEr   insertr~   r   __annotations__utilspec_from_file_location_specloadermodule_from_specexec_module	ExceptionrZ   insurance_crawlerr[   insurance_spiderr`   r	   r
   r   r   r   r   r   r   r   r   r   HTML_WITH_SCRIPTr1  r   intr   r   r(   r*   ri   r   r   r  rg   r    r   <module>r_     s  	    
     (^##%,,33::
 3zI-. / #hsm "	NN229c*yBX[gBg>hiEU\\5 ~~66u=  .  2 0  / =(

  ?

 %
	  		  S	 T8 T8xk kfgI gI^`) `)P~ ~Q  		s   A,E= =FF