
    Ti{              	       *   U d Z ddlZddlmc m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.                  j1                  d eedz               dZee   ed<   	 e	j8                  j;                  d eedz  dz              ZeBej>                  6e	j8                  jA                  e      Zej>                  jC                  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/m0Z0 dZ1dZ2dZ3dZ4dZ5	 	 d#dedede6defdZ7dedede8e   fdZ9 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)$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        ;/home/jay/workspace/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                    d}t         |u}|st        j                  d|fdt         |f      dt        j                         v st        j
                  t               rt        j                  t               ndt        j                  |      dz  }t        j                  d      dz   d|iz  }t        t        j                  |            dx}}y)	u<   browser.py가 importlib으로 정상 로드되어야 한다.Nis notz%(py0)s is not %(py3)sr   py0py3u+   browser.py를 로드하지 못했습니다.z
>assert %(py5)spy5)
r   
@pytest_ar_call_reprcompare@py_builtinslocals_should_repr_global_name	_saferepr_format_assertmsgAssertionError_format_explanationself@py_assert2@py_assert1@py_format4@py_format6s        r   test_browser_module_loadedz2TestImportCompatibility.test_browser_module_loaded   sd    #'V|4'VVV|4VVVVVV|VVV|VVV4VVV)VVVVVVVr   c                 Z   d}t         |u}|st        j                  d|fdt         |f      dt        j                         v st        j
                  t               rt        j                  t               ndt        j                  |      dz  }dd|iz  }t        t        j                  |            dx}}d}t        t         |      }|sd	d
t        j                         v st        j
                  t              rt        j                  t              nd
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)u9   browser.py에 STEALTH_ARGS 변수가 존재해야 한다.Nr+   r-   r   r.   assert %(py5)sr1   STEALTH_ARGS5assert %(py5)s
{%(py5)s = %(py0)s(%(py1)s, %(py3)s)
}hasattrr/   py1r0   r1   
r   r2   r3   r4   r5   r6   r7   r9   r:   rF   r<   r=   r>   r?   r@   @py_assert4s         r    test_browser_stealth_args_existsz8TestImportCompatibility.test_browser_stealth_args_exists       #''|4''''|4''''''|'''|'''4'''''''%34w|^44444444w444w444444|444|444^4444444444r   c                 Z   d}t         |u}|st        j                  d|fdt         |f      dt        j                         v st        j
                  t               rt        j                  t               ndt        j                  |      dz  }dd|iz  }t        t        j                  |            dx}}d}t        t         |      }|sd	d
t        j                         v st        j
                  t              rt        j                  t              nd
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)u9   browser.py에 HARMFUL_ARGS 변수가 존재해야 한다.Nr+   r-   r   r.   rC   r1   HARMFUL_ARGSrE   rF   rG   rI   rJ   s         r    test_browser_harmful_args_existsz8TestImportCompatibility.test_browser_harmful_args_exists   rM   r   c                 l   d}t         |u}|st        j                  d|fdt         |f      dt        j                         v st        j
                  t               rt        j                  t               ndt        j                  |      dz  }dd|iz  }t        t        j                  |            dx}}t         j                  }t        |      }|sdd	t        j                         v st        j
                  t              rt        j                  t              nd	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)uE   browser.py의 generate_stealth_headers가 호출 가능해야 한다.Nr+   r-   r   r.   rC   r1   zZassert %(py5)s
{%(py5)s = %(py0)s(%(py3)s
{%(py3)s = %(py1)s.generate_stealth_headers
})
}callablerG   )r   r2   r3   r4   r5   r6   r7   r9   r:   generate_stealth_headersrR   rJ   s         r   .test_browser_generate_stealth_headers_callablezFTestImportCompatibility.test_browser_generate_stealth_headers_callable   s    #''|4''''|4''''''|'''|'''4'''''''$==>x=>>>>>>>>x>>>x>>>>>>>>>>>>=>>>>>>>>>>r   c                 l   d}t         |u}|st        j                  d|fdt         |f      dt        j                         v st        j
                  t               rt        j                  t               ndt        j                  |      dz  }dd|iz  }t        t        j                  |            dx}}t         j                  }t        |      }|sdd	t        j                         v st        j
                  t              rt        j                  t              nd	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)u?   browser.py의 get_google_referer가 호출 가능해야 한다.Nr+   r-   r   r.   rC   r1   zTassert %(py5)s
{%(py5)s = %(py0)s(%(py3)s
{%(py3)s = %(py1)s.get_google_referer
})
}rR   rG   )r   r2   r3   r4   r5   r6   r7   r9   r:   get_google_refererrR   rJ   s         r   (test_browser_get_google_referer_callablez@TestImportCompatibility.test_browser_get_google_referer_callable   s    #''|4''''|4''''''|'''|'''4'''''''$778x788888888x888x88888888888878888888888r   c                 l   d}t         |u}|st        j                  d|fdt         |f      dt        j                         v st        j
                  t               rt        j                  t               ndt        j                  |      dz  }dd|iz  }t        t        j                  |            dx}}t         j                  }t        |      }|sdd	t        j                         v st        j
                  t              rt        j                  t              nd	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)uD   browser.py의 create_resource_blocker가 호출 가능해야 한다.Nr+   r-   r   r.   rC   r1   zYassert %(py5)s
{%(py5)s = %(py0)s(%(py3)s
{%(py3)s = %(py1)s.create_resource_blocker
})
}rR   rG   r   r2   r3   r4   r5   r6   r7   r9   r:   create_resource_blockerrR   rJ   s         r   -test_browser_create_resource_blocker_callablezETestImportCompatibility.test_browser_create_resource_blocker_callable   s    #''|4''''|4''''''|'''|'''4'''''''$<<=x<========x===x============<==========r   c                 Z   d}t         |u}|st        j                  d|fdt         |f      dt        j                         v st        j
                  t               rt        j                  t               ndt        j                  |      dz  }dd|iz  }t        t        j                  |            dx}}d}t        t         |      }|sd	d
t        j                         v st        j
                  t              rt        j                  t              nd
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)uC   browser.py에 BLOCKED_RESOURCE_TYPES 변수가 존재해야 한다.Nr+   r-   r   r.   rC   r1   BLOCKED_RESOURCE_TYPESrE   rF   rG   rI   rJ   s         r   *test_browser_blocked_resource_types_existszBTestImportCompatibility.test_browser_blocked_resource_types_exists   s    #''|4''''|4''''''|'''|'''4'''''''%=>w|%=>>>>>>>>w>>>w>>>>>>|>>>|>>>%=>>>>>>>>>>r   c                 n   d}t         |u}|st        j                  d|fdt         |f      dt        j                         v st        j
                  t               rt        j                  t               ndt        j                  |      dz  }dd|iz  }t        t        j                  |            dx}}y)u7   crawl_utils.ProxyRotator가 import 가능해야 한다.Nr+   r-   r	   r.   rC   r1   )	r	   r2   r3   r4   r5   r6   r7   r9   r:   r;   s        r   )test_crawl_utils_proxy_rotator_importablezATestImportCompatibility.test_crawl_utils_proxy_rotator_importable   s[    #''|4''''|4''''''|'''|'''4'''''''r   c                    t        t              }|sddt        j                         v st	        j
                  t               rt	        j                  t               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)u9   crawl_utils.is_proxy_error가 import 가능해야 한다.,assert %(py3)s
{%(py3)s = %(py0)s(%(py1)s)
}rR   r   r/   rH   r0   N)	rR   r   r4   r5   r2   r6   r7   r9   r:   r<   r=   r?   s      r   *test_crawl_utils_is_proxy_error_importablezBTestImportCompatibility.test_crawl_utils_is_proxy_error_importable   si    ''''''''x'''x'''''''''''''''''''r   c                    t        t              }|sddt        j                         v st	        j
                  t               rt	        j                  t               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;   crawl_utils.fetch_with_retry가 import 가능해야 한다.rb   rR   r   rc   N)	rR   r   r4   r5   r2   r6   r7   r9   r:   rd   s      r   ,test_crawl_utils_fetch_with_retry_importablezDTestImportCompatibility.test_crawl_utils_fetch_with_retry_importable   l    ())))))))x)))x))))))()))())))))))))r   c                    t        t              }|sddt        j                         v st	        j
                  t               rt	        j                  t               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)uC   crawl_utils.get_resource_block_types가 import 가능해야 한다.rb   rR   r   rc   N)	rR   r   r4   r5   r2   r6   r7   r9   r:   rd   s      r   4test_crawl_utils_get_resource_block_types_importablezLTestImportCompatibility.test_crawl_utils_get_resource_block_types_importable   sl    011111111x111x111111011101111111111r   c                    t        t              }|sddt        j                         v st	        j
                  t               rt	        j                  t               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;   crawl_utils.html_to_markdown가 import 가능해야 한다.rb   rR   r   rc   N)	rR   r   r4   r5   r2   r6   r7   r9   r:   rd   s      r   ,test_crawl_utils_html_to_markdown_importablezDTestImportCompatibility.test_crawl_utils_html_to_markdown_importable   rh   r   c                    t        t              }|sddt        j                         v st	        j
                  t               rt	        j                  t               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)u5   crawl_utils.clean_html이 import 가능해야 한다.rb   rR   r
   rc   N)	rR   r
   r4   r5   r2   r6   r7   r9   r:   rd   s      r   &test_crawl_utils_clean_html_importablez>TestImportCompatibility.test_crawl_utils_clean_html_importable   si    
########x###x######
###
##########r   c                 n   d}t         |u}|st        j                  d|fdt         |f      dt        j                         v st        j
                  t               rt        j                  t               ndt        j                  |      dz  }dd|iz  }t        t        j                  |            dx}}y)uA   insurance_crawler.InsuranceCrawler가 import 가능해야 한다.Nr+   r-   r   r.   rC   r1   )	r   r2   r3   r4   r5   r6   r7   r9   r:   r;   s        r   !test_insurance_crawler_importablez9TestImportCompatibility.test_insurance_crawler_importable   s_    '++t++++t++++++++++++t+++++++r   c                 n   d}t         |u}|st        j                  d|fdt         |f      dt        j                         v st        j
                  t               rt        j                  t               ndt        j                  |      dz  }dd|iz  }t        t        j                  |            dx}}y)u?   insurance_spider.InsuranceSpider가 import 가능해야 한다.Nr+   r-   r   r.   rC   r1   )	r   r2   r3   r4   r5   r6   r7   r9   r:   r;   s        r    test_insurance_spider_importablez8TestImportCompatibility.test_insurance_spider_importable   [    &**d****d************d*******r   c                 n   d}t         |u}|st        j                  d|fdt         |f      dt        j                         v st        j
                  t               rt        j                  t               ndt        j                  |      dz  }dd|iz  }t        t        j                  |            dx}}y)u?   insurance_spider.ResponseHistory가 import 가능해야 한다.Nr+   r-   r   r.   rC   r1   )	r   r2   r3   r4   r5   r6   r7   r9   r:   r;   s        r    test_response_history_importablez8TestImportCompatibility.test_response_history_importable   rs   r   c                    t         j                  }t        j                  }||u }|st        j                  d|fd||f      dt        j                         v st        j                  |      rt        j                  |      nddt        j                         v st        j                  |      rt        j                  |      nddz  }dd|iz  }t        t        j                  |            d}y)	uM   crawl_utils와 insurance_crawler 사이에 이름 충돌이 없어야 한다.is)z%(py0)s is %(py2)scu_ProxyRotatoric_ProxyRotatorr/   py2assert %(py4)spy4N)crawl_utilsr	   _insurance_crawler_modr2   r3   r4   r5   r6   r7   r9   r:   )r<   ry   rz   r>   @py_format3@py_format5s         r   %test_no_name_conflict_between_modulesz=TestImportCompatibility.test_no_name_conflict_between_modules   s     &220==/1111/111111111111111/111/1111111r   c                    t         t        u}|st        j                  d|fdt         t        f      dt	        j
                         v st        j                  t               rt        j                  t               nddt	        j
                         v st        j                  t              rt        j                  t              nddz  }dd|iz  }t        t        j                  |            d}t         t        u}|st        j                  d|fdt         t        f      dt	        j
                         v st        j                  t               rt        j                  t               ndd	t	        j
                         v st        j                  t              rt        j                  t              nd	dz  }dd|iz  }t        t        j                  |            d}t        t        u}|st        j                  d|fdt        t        f      dt	        j
                         v st        j                  t              rt        j                  t              ndd	t	        j
                         v st        j                  t              rt        j                  t              nd	dz  }dd|iz  }t        t        j                  |            d}y)
u7   각 모듈은 서로 다른 모듈 객체여야 한다.r+   )z%(py0)s is not %(py2)sr   _insurance_spider_modr{   r}   r~   Nr   )r   r   r2   r3   r4   r5   r6   r7   r9   r:   r   )r<   r>   r   r   s       r   !test_all_modules_distinct_objectsz9TestImportCompatibility.test_all_modules_distinct_objects   sG   %-BBBBB%-BBBBBBB%BBB%BBBBBB-BBBB-BBBBBBBB%[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8888$K7777$K777777$777$777777K777K7777777r   r   N)__name__
__module____qualname____doc__rA   rL   rP   rT   rW   r[   r^   r`   re   rg   rj   rl   rn   rp   rr   ru   r   r    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                 
   d}t         |u}|st        j                  d|fdt         |f      dt        j                         v st        j
                  t               rt        j                  t               ndt        j                  |      dz  }dd|iz  }t        t        j                  |            dx}}t         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
                  t               rt        j                  t               ndt        j                  |      d
t        j                         v st        j
                  t              rt        j                  t              nd
t        j                  |      dz  }t        t        j                  |            dx}}y)u4   browser.py의 STEALTH_ARGS는 tuple이어야 한다.Nr+   r-   r   r.   rC   r1   zWassert %(py6)s
{%(py6)s = %(py0)s(%(py3)s
{%(py3)s = %(py1)s.STEALTH_ARGS
}, %(py4)s)
}
isinstancetupler/   rH   r0   r~   py6)r   r2   r3   r4   r5   r6   r7   r9   r:   rD   r   r   r<   r=   r>   r?   r@   @py_assert5@py_format7s          r   test_stealth_args_is_tuplez3TestInterfaceConsistency.test_stealth_args_is_tuple       #''|4''''|4''''''|'''|'''4'''''''&33;z3U;;;;;;;;z;;;z;;;;;;,;;;,;;;3;;;;;;U;;;U;;;;;;;;;;r   c                 
   d}t         |u}|st        j                  d|fdt         |f      dt        j                         v st        j
                  t               rt        j                  t               ndt        j                  |      dz  }dd|iz  }t        t        j                  |            dx}}t         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
                  t               rt        j                  t               ndt        j                  |      d
t        j                         v st        j
                  t              rt        j                  t              nd
t        j                  |      dz  }t        t        j                  |            dx}}y)u4   browser.py의 HARMFUL_ARGS는 tuple이어야 한다.Nr+   r-   r   r.   rC   r1   zWassert %(py6)s
{%(py6)s = %(py0)s(%(py3)s
{%(py3)s = %(py1)s.HARMFUL_ARGS
}, %(py4)s)
}r   r   r   )r   r2   r3   r4   r5   r6   r7   r9   r:   rO   r   r   r   s          r   test_harmful_args_is_tuplez3TestInterfaceConsistency.test_harmful_args_is_tuple   r   r   c                 
   d}t         |u}|st        j                  d|fdt         |f      dt        j                         v st        j
                  t               rt        j                  t               ndt        j                  |      dz  }dd|iz  }t        t        j                  |            dx}}t         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
                  t               rt        j                  t               ndt        j                  |      d
t        j                         v st        j
                  t              rt        j                  t              nd
t        j                  |      dz  }t        t        j                  |            dx}}y)u<   browser.py의 BLOCKED_RESOURCE_TYPES는 set이어야 한다.Nr+   r-   r   r.   rC   r1   zaassert %(py6)s
{%(py6)s = %(py0)s(%(py3)s
{%(py3)s = %(py1)s.BLOCKED_RESOURCE_TYPES
}, %(py4)s)
}r   setr   )r   r2   r3   r4   r5   r6   r7   r9   r:   r]   r   r   r   s          r   "test_blocked_resource_types_is_setz;TestInterfaceConsistency.test_blocked_resource_types_is_set   s    #''|4''''|4''''''|'''|'''4'''''''&==Cz=sCCCCCCCCzCCCzCCCCCC,CCC,CCC=CCCCCCsCCCsCCCCCCCCCCr   c                 6   ddg}t        |      }|j                  }d}||u}|st        j                  d|fd||f      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}}|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t        j                  |      dt	        j
                         v st        j                  t              rt        j                  t              ndt        j                  |	      dz  }
t        t        j                  |
            dx}}	y)u`   InsuranceCrawler가 proxy_list로 생성할 때 crawl_utils.ProxyRotator를 사용해야 한다.zhttp://proxy1:8080zhttp://proxy2:8080
proxy_listNr+   z9%(py2)s
{%(py2)s = %(py0)s.proxy_rotator
} is not %(py5)scrawlerr/   r|   r1   assert %(py7)spy7zXassert %(py6)s
{%(py6)s = %(py0)s(%(py3)s
{%(py3)s = %(py1)s.proxy_rotator
}, %(py4)s)
}r   r	   r   )r   proxy_rotatorr2   r3   r4   r5   r6   r7   r9   r:   r   r	   )r<   proxiesr   r>   rK   @py_assert3r@   @py_format8r=   r   r   s              r   :test_insurance_crawler_uses_proxy_rotator_from_crawl_utilszSTestInterfaceConsistency.test_insurance_crawler_uses_proxy_rotator_from_crawl_utils   s)   ')=>"g6$$0D0$D0000$D000000w000w000$000D0000000!//>z/>>>>>>>>z>>>z>>>>>>'>>>'>>>/>>>>>>>>>>>>>>>>>>>r   c                    t               }d}|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}||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)ua   InsuranceCrawler.to_llm_input()은 clean_html→html_to_markdown 파이프라인을 사용한다.u3   <p>보험 공시 정보</p><script>evil();</script>5assert %(py4)s
{%(py4)s = %(py0)s(%(py1)s, %(py2)s)
}r   resultstrr/   rH   r|   r~   Nu   보험 공시 정보inz%(py1)s in %(py3)srH   r0   rC   r1   evil()not inz%(py1)s not in %(py3)s)r   to_llm_inputr   r   r4   r5   r2   r6   r7   r9   r:   r3   )
r<   r   r   r   r   r   @py_assert0r=   r?   r@   s
             r   ?test_insurance_crawler_clean_html_and_html_to_markdown_pipelinezXTestInterfaceConsistency.test_insurance_crawler_clean_html_and_html_to_markdown_pipeline   s\   "$D%%d+&#&&&&&&&&z&&&z&&&&&&&&&&&&&&&&&#&&&#&&&&&&&&&&%/%////%///%////////////////%xv%%%%xv%%%x%%%%%%v%%%v%%%%%%%r   c                 v   t        dg      }d}t        ||      }|sd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  }t        t	        j                  |            dx}}|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t	        j                  |      dt        j                         v st	        j
                  t              rt	        j                  t              ndt	        j                  |      dz  }t        t	        j                  |            dx}}y)uV   InsuranceSpider 인스턴스에 _crawler 속성(InsuranceCrawler)이 있어야 한다.https://example.com
start_urls_crawlerrE   rF   r    rG   NSassert %(py6)s
{%(py6)s = %(py0)s(%(py3)s
{%(py3)s = %(py1)s._crawler
}, %(py4)s)
}r   r   r   r   rF   r4   r5   r2   r6   r7   r9   r:   r   r   r   r<   r    r=   rK   r@   r   r   s          r   +test_insurance_spider_has_crawler_attributezDTestInterfaceConsistency.test_insurance_spider_has_crawler_attribute  (    -B,CD)*wvz********w***w******v***v***z********** //<z/+;<<<<<<<<z<<<z<<<<<<&<<<&<<</<<<<<<+;<<<+;<<<<<<<<<<r   c                 v   t        dg      }d}t        ||      }|sd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  }t        t	        j                  |            dx}}|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t	        j                  |      dt        j                         v st	        j
                  t              rt	        j                  t              ndt	        j                  |      dz  }t        t	        j                  |            dx}}y)u]   InsuranceSpider 인스턴스에 response_history 속성(ResponseHistory)이 있어야 한다.r   r   response_historyrE   rF   r    rG   Nz[assert %(py6)s
{%(py6)s = %(py0)s(%(py3)s
{%(py3)s = %(py1)s.response_history
}, %(py4)s)
}r   r   r   )r   rF   r4   r5   r2   r6   r7   r9   r:   r   r   r   r   s          r   4test_insurance_spider_has_response_history_attributezMTestInterfaceConsistency.test_insurance_spider_has_response_history_attribute
  s+    -B,CD12wv122222222w222w222222v222v22212222222222 11Cz1?CCCCCCCCzCCCzCCCCCC&CCC&CCC1CCCCCC?CCC?CCCCCCCCCCr   c                    t               }|j                  dddgddi       |j                         }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}}y)uW   ResponseHistory.record()는 url, status, redirects, headers 인자를 받아야 한다.r      zhttps://old.example.comzContent-Typez	text/html)	redirectsheaders   ==z0%(py3)s
{%(py3)s = %(py0)s(%(py1)s)
} == %(py6)slenr   r/   rH   r0   r   assert %(py8)spy8N)r   recordget_historyr   r2   r3   r4   r5   r6   r7   r9   r:   )r<   historyr   r=   r   rK   r   @py_format9s           r   -test_response_history_record_method_signaturezFTestInterfaceConsistency.test_response_history_record_method_signature  s    !#!01#[1	 	 	
 $$&6{a{a{ass66{ar   c           	         t               }|j                  dd       t        j                         5 }t	        t        |      dz        }|j                  |       t        |      }|j                  } |       }|sd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                  |      t        j                  |      dz  }t        t        j                  |            dx}x}}t        |      d	z  }|j                  |       |j                  }	 |	       }
|
sd
dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |	      t        j                  |
      dz  }t        t        j                  |            dx}	}
ddd       y# 1 sw Y   yxY w)uE   ResponseHistory.save()는 str 또는 Path 인자를 받아야 한다.r   r   zhistory_str.jsonz_assert %(py7)s
{%(py7)s = %(py5)s
{%(py5)s = %(py3)s
{%(py3)s = %(py0)s(%(py1)s)
}.exists
}()
}r   path_str)r/   rH   r0   r1   r   Nzhistory_path.jsonAassert %(py4)s
{%(py4)s = %(py2)s
{%(py2)s = %(py0)s.exists
}()
}path_objr/   r|   r~   )r   r   tempfileTemporaryDirectoryr   r   saveexistsr4   r5   r2   r6   r7   r9   r:   )r<   r   tmpdirr   r=   rK   @py_assert6r   r   r>   r   r   s               r   $test_response_history_save_signaturez=TestInterfaceConsistency.test_response_history_save_signature  sb   !#,c2((* 	%f4<*<<=HLL">*>((*(********4***4************>***(**********F|&99HLL"??$?$$$$$$$$8$$$8$$$?$$$$$$$$$$	% 	% 	%s   G>H88Ic                 B   d}t         |u}|st        j                  d|fdt         |f      dt        j                         v st        j
                  t               rt        j                  t               ndt        j                  |      dz  }dd|iz  }t        t        j                  |            dx}}t        d      }t         j                  }||k(  }|st        j                  d	|fd
||f      dt        j                         v st        j
                  |      rt        j                  |      nddt        j                         v st        j
                  |      rt        j                  |      nddz  }dd|iz  }t        t        j                  |            d}y)uf   crawl_utils.get_resource_block_types('default')와 browser.BLOCKED_RESOURCE_TYPES가 같아야 한다.Nr+   r-   r   r.   rC   r1   defaultr   )z%(py0)s == %(py2)sdefault_typesbrowser_typesr{   r}   r~   )r   r2   r3   r4   r5   r6   r7   r9   r:   r   r]   )	r<   r=   r>   r?   r@   r   r   r   r   s	            r   <test_get_resource_block_types_matches_blocked_resource_typeszUTestInterfaceConsistency.test_get_resource_block_types_matches_blocked_resource_types+  s    #''|4''''|4''''''|'''|'''4'''''''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\  }	|	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}	}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   >=z0%(py3)s
{%(py3)s = %(py0)s(%(py1)s)
} >= %(py6)sr   itemsr   r   r   Nr   r   HTML_PRODUCTSasynciorunr'   r   dictr   r2   r3   r4   r5   r6   r7   r9   r:   r<   configr    r!   r%   rr   r=   r   rK   r   r   s               r   6test_insurance_spider_extract_with_selector_delegationzOTestInterfaceConsistency.test_insurance_spider_extract_with_selector_delegation2  s     &w'

 !-.$
 '}:OP++4VXFG#;qz!T':;;5zQzQzQss55zQ <   E8!E8c                    ddd}t        dg|      }t        t        d      }t        j                  t        ||            }|D cg c]  }t        |t              s| }}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}	}yc c}w )u\   InsuranceSpider.parse()에서 table 모드는 _crawler.extract_table을 호출해야 한다.tabler   table_selectorr   r   r   r   r   r   r   r   r   r   r   N)r   r   
HTML_TABLEr   r   r'   r   r   r   r2   r3   r4   r5   r6   r7   r9   r:   r   s               r   .test_insurance_spider_extract_table_delegationzGTestInterfaceConsistency.test_insurance_spider_extract_table_delegationB  s    !W= -.$
 'z7LM++4VXFG#;qz!T':;;5zQzQzQss55zQ <s   E5E5r   )r   r   r   r   r   r   r   r   r   r   r   r   r   r   r  r	  r   r   r   r   r      sC    `<
<
D
?&=D %. 
r   r   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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        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   }t	        |t              }|sdd	t        j                         v st        j                  t              rt        j                  t              nd	t        j                  |      dt        j                         v st        j                  t              rt        j                  t              ndt        j                  |      dz  }t        t        j                  |            dx}}|d   d   }d}||k(  }|slt        j                  d|fd||f      t        j                  |      t        j                  |      dz  }dd|iz  }	t        t        j                  |	            dx}x}}|d   d   }d }||k(  }|slt        j                  d|fd||f      t        j                  |      t        j                  |      dz  }dd|iz  }	t        t        j                  |	            dx}x}}y)!u^   HTML → parse() → extract_with_selector() → 결과 dict 플로우가 동작해야 한다.Fr   r   r   .pricer   pricer   r   r   r   r   listr   N   r   r   r   r   r   r   r   5assert %(py5)s
{%(py5)s = %(py0)s(%(py2)s, %(py3)s)
}r   r/   r|   r0   r1   r      화재보험z%(py1)s == %(py4)srH   r~   assert %(py6)sr   r  50000)r   r#   r   extract_with_selectorr   r  r4   r5   r2   r6   r7   r9   r:   r   r3   r   r<   r   pager   r   r   r=   r   rK   r   r   r>   r@   r   s                 r   2test_html_parse_extract_with_selector_returns_dictzJTestDataFlowIntegration.test_html_parse_extract_with_selector_returns_dictW  s|   "E2}}]+..##h7 / 

 &$''''''''z'''z''''''&'''&''''''$'''$''''''''''6{a{a{ass66{a )*z)T********z***z***)******T***T**********ay 2N2 N2222 N222 222N2222222ay!,W,!W,,,,!W,,,!,,,W,,,,,,,r   c                    t        d      }|j                  t              }|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        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   }t	        |t              }|sddt        j                         v st        j                  t              rt        j                  t              ndt        j                  |      dt        j                         v st        j                  t              rt        j                  t              ndt        j                  |      dz  }t        t        j                  |            dx}}|d   d   }d}||k(  }|slt        j                  d
|fd||f      t        j                  |      t        j                  |      dz  }dd|iz  }	t        t        j                  |	            dx}x}}|d   d   }d}||k(  }|slt        j                  d
|fd||f      t        j                  |      t        j                  |      dz  }dd|iz  }	t        t        j                  |	            dx}x}}y)uV   HTML → parse() → extract_table() → 결과 dict 플로우가 동작해야 한다.Fr  r   r   r   r  r   N   r   r   r   r   r   r   r   r  r   r  	   보험명r  r  r  r  r   u	   보험료r  )r   r#   r  extract_tabler   r  r4   r5   r2   r6   r7   r9   r:   r   r3   r   r  s                 r   *test_html_parse_extract_table_returns_dictzBTestDataFlowIntegration.test_html_parse_extract_table_returns_dictf  sg   "E2}}Z(&&t,&$''''''''z'''z''''''&'''&''''''$'''$''''''''''6{a{a{ass66{a )*z)T********z***z***)******T***T**********ay%77%7777%777%7777777777ay%00%0000%000%0000000000r   c                    t               }|j                  t              }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}||kD  }|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}}g }
d}||v }|}|sd}||v }|}|sXt        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  }|
j                  |       |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  }|
j                  |       t        j                  |
d      i z  }dd|iz  }t        t        j                  |            dx}x}
x}x}x}}y)uP   HTML → to_llm_input() → markdown 문자열 플로우가 동작해야 한다.r   r   r   r   r   Nr   )>)z/%(py3)s
{%(py3)s = %(py0)s(%(py1)s)
} > %(py6)sr   r   r   r   r  u   보험r   )z%(py3)s in %(py5)s)r0   r1   z%(py7)sr   )z%(py10)s in %(py12)s)py10py12z%(py14)spy14r   zassert %(py17)spy17)r   r   r   r   r   r4   r5   r2   r6   r7   r9   r:   r   r3   r$   _format_boolop)r<   r   r   r   r   r=   r   rK   r   r   r>   r   @py_assert9@py_assert11r@   r   @py_format13@py_format15@py_format16@py_format18s                       r   .test_html_to_llm_input_returns_markdown_stringzFTestDataFlowIntegration.test_html_to_llm_input_returns_markdown_stringq  s   "$%%m4&#&&&&&&&&z&&&z&&&&&&&&&&&&&&&&&#&&&#&&&&&&&&&&6{Q{Q{Qss66{Q=~=~'=8=8v+=====~===~================8v===8======v===v==============r   c                    t               }|j                  dddg       |j                  dd       |j                         }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}}|j                  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  }t        t	        j                  |            dx}x}}|d   d   }	d}
|	|
k(  }|slt	        j
                  d|fd|	|
f      t	        j                  |	      t	        j                  |
      dz  }dd|iz  }t        t	        j                  |            dx}	x}}
|j                  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  }t        t	        j                  |            dx}x}}t        j                         5 }t        |      dz  }|j!                  |       |j"                  } |       }
|
sddt        j                         v st	        j                  |      rt	        j                  |      ndt	        j                  |      t	        j                  |
      dz  }t        t	        j                  |            dx}}
t%        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}
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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   all_histr   r   r   Nr   r   r   chain_finalr   r   r  r  r  r   	chain_oldzhistory.jsonr   pathr   utf-8encodingr   r   datar  r   )r   r   r   r   r2   r3   r4   r5   r6   r7   r9   r:   	get_chainr   r   r   r   r   jsonloads	read_textr   r  )r<   r   r3  r=   r   rK   r   r   r4  r   r   r   r5  r   r6  r>   r:  s                    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}!!}!!!!}!!!!!!s!!!s!!!!!!8!!!8!!!}!!!!!!!!!!''(CD;$1$1$$$$1$$$$$$s$$$s$$$$$$;$$$;$$$$$$1$$$$$$$1~e$C(CC$(CCCCC$(CCCC$CCC(CCCCCCCC%%&?@	9~""~""""~""""""s"""s""""""9"""9"""~""""""""""((* 	"f<.0DLL;; ;= =     4   4   ;   =      ::dnngn>?DdD)))))))):))):))))))d)))d))))))D)))D))))))))))t9!!9>!!!9!!!!!!3!!!3!!!!!!t!!!t!!!9!!!!!!!!!!	" 	" 	"s   +L]]c                    d}t        |      }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}}t        |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}||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	)uR   clean_html() → html_to_markdown() 파이프라인이 정상 동작해야 한다.u?   <p>보험 정보</p><script>evil();</script><style>.a{}</style>r   r   r   cleanedr   rC   r1   NF)remove_noiser   r   markdownr   r   u   보험 정보r   r   )r
   r2   r3   r7   r4   r5   r6   r9   r:   r   r   r   )
r<   raw_htmlrA  r   r=   r?   r@   rC  r   r   s
             r   .test_clean_html_then_html_to_markdown_pipelinezFTestDataFlowIntegration.test_clean_html_then_html_to_markdown_pipeline  sW   TX&&xw&&&&xw&&&x&&&&&&w&&&w&&&&&&&#G%@(C((((((((z(((z(((((((((((((((((C(((C((((((((((*(****(*********(***(*******r   c                    ddg}t        |      }|j                  }d}||u}|st        j                  d|fd||f      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}}|j                  j                         }|j                  j                         }	d}
||
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}}
d}
|	|
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)u[   InsuranceCrawler에 proxy_list 전달 시 ProxyRotator가 올바르게 동작해야 한다.zhttp://p1:8080zhttp://p2:8080r   Nr+   r   r   r   r   r   r   z%(py0)s == %(py3)sfirstr.   rC   r1   second)r   r   r2   r3   r4   r5   r6   r7   r9   r:   get_next)r<   r   r   r>   rK   r   r@   r   rH  rI  r=   r?   s               r   *test_proxy_rotator_round_trip_with_crawlerzBTestDataFlowIntegration.test_proxy_rotator_round_trip_with_crawler  si   #%56"g6$$0D0$D0000$D000000w000w000$000D0000000%%..0&&//1((u(((((u(((((((u(((u((((((((((())v)))))v)))))))v)))v)))))))))))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\  }	|	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        }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}}|d   d   }d	}||k(  }|slt        j                  d|fd||f      t        j                  |      t        j                  |      dz  }dd|iz  }
t        t        j                   |
            dx}x}}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   r   r   r   r   r   r   r   Nc              3   $   K   | ]  }d |v  
 yw)_source_urlNr   ).0r&   s     r   	<genexpr>zOTestDataFlowIntegration.test_spider_parse_css_mode_full_flow.<locals>.<genexpr>  s     ;T=D(;s   ,assert %(py4)s
{%(py4)s = %(py0)s(%(py2)s)
}allr   r   rN  r   r  r  r  r   )r   r   r   r   r   r'   r   r   r   r2   r3   r4   r5   r6   r7   r9   r:   rR  )r<   r   r    r!   r%   r  r   r=   r   rK   r   r   r>   r   r   r   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':;;5zQzQzQss55zQ;U;;s;;;;;;;;;s;;;s;;;;;;;;;;;;;;Qx&H*HH&*HHHHH&*HHHH&HHH*HHHHHHHH	 <s   J+"J+r   )r   r   r   r   r  r#  r1  r?  rE  rK  rS  r   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                 v   t        dg      }d}t        ||      }|sd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  }t        t	        j                  |            dx}}|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t	        j                  |      dt        j                         v st	        j
                  t              rt	        j                  t              ndt	        j                  |      dz  }t        t	        j                  |            dx}}y)uP   InsuranceSpider 초기화 시 InsuranceCrawler가 내부 생성되어야 한다.r   r   r   rE   rF   r    rG   Nr   r   r   r   r   r   s          r   5test_spider_init_creates_insurance_crawler_internallyzRTestSpiderCrawlerIntegration.test_spider_init_creates_insurance_crawler_internally  r   r   c                    t        dg      }|j                  }|j                  }d}||u }|st        j                  d|fd||f      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      t        j                  |      dz  }dd	|iz  }t        t        j                  |            d
x}x}x}}y
)uR   InsuranceSpider 내부의 _crawler는 adaptive=False로 초기화되어야 한다.r   r   Frw   )zN%(py4)s
{%(py4)s = %(py2)s
{%(py2)s = %(py0)s._crawler
}.adaptive
} is %(py7)sr    r/   r|   r~   r   assert %(py9)spy9N)r   r   r   r2   r3   r4   r5   r6   r7   r9   r:   )r<   r    r>   r   r   r   r   @py_format10s           r   "test_spider_crawler_adaptive_falsez?TestSpiderCrawlerIntegration.test_spider_crawler_adaptive_false  s     -B,CD0''050'50000'5000000v000v000000'00050000000r   c                    ddddid}t        dg|      }|j                  }d}||u}|st        j                  d	|fd
||f      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}}|j                  d   }d}||k(  }	|	slt        j                  d|	fd||f      t        j                  |      t        j                  |      dz  }
dd|
iz  }t        t        j                  |            dx}x}	}t        t              }t        j                  t        ||            }|D cg c]  }t        |t               s| }}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c c}w ) ud   extraction_config mode=css 전달 시 parse()에서 extract_with_selector가 선택되어야 한다.r   r   r   r   r   r   r   Nr+   )z=%(py2)s
{%(py2)s = %(py0)s.extraction_config
} is not %(py5)sr    r   r   r   r   r   r  r  r  r   r   r   r   r   r   r   r   r   r   r   )z%(py1)s in %(py4)s)r   r   r2   r3   r4   r5   r6   r7   r9   r:   r   r   r   r   r'   r   r   r   )r<   r   r    r>   rK   r   r@   r   r   r=   r   r   r!   r%   r  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'

 !-.$
 ''3t3't3333't333333v333v333'333t3333333''/858/58888/5888/88858888888&}5++4VXFG#;qz!T':;;5zQzQzQss55zQ!q!v!!!!v!!!v!!!!!!!!!! <s   M0Mc                 L   ddd}t        dg|      }t        t              }t        j                  t        ||            }|D cg c]  }t        |t              s| }}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}	}g }d}|d   }||v }	|	}|	sd}|d   }||v }|}|st        j                  d|	fd||f      t        j                  |      t        j                  |      dz  }
dd|
iz  }|j#                  |       |	s_t        j                  dfdf      t        j                  |      t        j                  |      dz  }dd|iz  }|j#                  |       t        j$                  |d      i z  }dd|iz  }t        t        j                   |            dx}x}x}x}	x}x}x}}yc c}w )u^   extraction_config mode=table 전달 시 parse()에서 extract_table이 선택되어야 한다.r  r  r   r   r   r   r   r   r   r   r   r   Nr!  r   col_0r   )z%(py3)s in %(py6)s)r0   r   z%(py8)s)z%(py11)s in %(py14)s)py11r(  z%(py16)spy16zassert %(py19)spy19)r   r   r  r   r   r'   r   r   r   r2   r3   r4   r5   r6   r7   r9   r:   r$   r*  )r<   r   r    r!   r%   r  r   r=   r   rK   r   r   r>   r   @py_assert10@py_assert13@py_assert12r.  @py_format17r0  @py_format20s                        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':;;5zQzQzQss55zQ={=eAh={h&='=U1X='X*===={h==={===h======='X==='===X============== <s   J!J!c                    ddd}t        dg|      }t        t              }t        j                  t        ||            }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)ub   extraction_config mode=similar 전달 시 parse()에서 extract_similar가 선택되어야 한다.similarr   )r   reference_selectorr   r   r   r   r%   r  r   N)r   r   r   r   r   r'   r   r  r4   r5   r2   r6   r7   r9   r:   )r<   r   r    r!   r%   r   r   s          r   *test_spider_extraction_config_similar_modezGTestSpiderCrawlerIntegration.test_spider_extraction_config_similar_mode  s     ",
 !-.$
 '}5++4VXFG'4((((((((z(((z(((((('((('((((((4(((4((((((((((r   c                 B   ddddid}t        dg|      }t        t        d	      }t        j                  t        ||             |j                  j                         }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        }
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)uD   parse() 호출 시 ResponseHistory에 URL이 기록되어야 한다.r   r   r   r   r   r   r   https://example.com/insurancer   r   r   r   r   r   r   r   r   Nc              3   ,   K   | ]  }|d    dk(    yw)r   rp  Nr   )rO  es     r   rP  z]TestSpiderCrawlerIntegration.test_spider_response_history_records_on_parse.<locals>.<genexpr>  s     P11U8>>Ps   rQ  anyr   )r   r   r   r   r   r'   r   r   r   r2   r3   r4   r5   r6   r7   r9   r:   rs  )r<   r   r    r!   r   r=   r   rK   r   r   r>   r   r   s                r   -test_spider_response_history_records_on_parsezJTestSpiderCrawlerIntegration.test_spider_response_history_records_on_parse   sI   PWGXY -.$
 '}:YZ*68<=))5577| q |q    |q      s   s      7   7   |   q       PPPsPPPPPPPPPsPPPsPPPPPPPPPPPPPPr   c                 6   t        j                         5 }t        |      dz  }t        dgt	        |            }t        j                  |j                  d             |j                  } |       }|sddt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      dz  }t        t        j                  |            d	x}}d	d	d	       y	# 1 sw Y   y	xY w)
u;   on_start() 실행 후 output_dir이 생성되어야 한다.
spider_outr   r   
output_dirFresumingr   rx  r   N)r   r   r   r   r   r   r   on_startr   r4   r5   r2   r6   r7   r9   r:   )r<   r   rx  r    r>   r   r   s          r   'test_spider_on_start_creates_output_dirzDTestSpiderCrawlerIntegration.test_spider_on_start_creates_output_dir  s    ((* 	'ff4J$12z?F KK78$$&$&&&&&&&&:&&&:&&&$&&&&&&&&&&	' 	' 	's   C1DDc                    t        j                         5 }t        dg|      }|j                  j	                  dd       t        j                  |j                  d             t        j                  |j                                t        |      dz  }|j                  } |       }|sddt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      d	z  }t!        t        j"                  |            d
x}}d
d
d
       y
# 1 sw Y   y
xY w)u[   on_close() 실행 후 response_history.json 파일이 output_dir에 저장되어야 한다.r   rw  r   Fry  zresponse_history.jsonr   history_filer   N)r   r   r   r   r   r   r   r{  on_closer   r   r4   r5   r2   r6   r7   r9   r:   )r<   r   r    r~  r>   r   r   s          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   D'EEr   )r   r   r   r   rW  r]  r_  rj  rn  rt  r|  r  r   r   r   rU  rU    s.    H=1
"&>)Q	')r   rU  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(  }|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)uO   빈 HTML에서 extract_with_selector()는 빈 리스트를 반환해야 한다.Fr  r   r   r   r  r   rG  r   r.   rC   r1   N)r   r#   
HTML_EMPTYr  r2   r3   r4   r5   r6   r7   r9   r:   r<   r   r  r   r=   r>   r?   r@   s           r   8test_empty_html_extract_with_selector_returns_empty_listzFTestEdgeCases.test_empty_html_extract_with_selector_returns_empty_list.  s    "E2}}Z(..#G$ / 

 v|vvvr   c                    t        d      }|j                  t              }|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	)
uG   빈 HTML에서 extract_table()은 빈 리스트를 반환해야 한다.Fr  r   rG  r   r.   rC   r1   N)r   r#   r  r"  r2   r3   r4   r5   r6   r7   r9   r:   r  s           r   0test_empty_html_extract_table_returns_empty_listz>TestEdgeCases.test_empty_html_extract_table_returns_empty_list9  s    "E2}}Z(&&t,v|vvvr   c                 L   t               }|j                  t              }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                  } |       }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                  |      d
z  }dd|iz  }	t        t        j                  |	            dx}x}x}}y)uF   빈 HTML에서 to_llm_input()은 빈 문자열을 반환해야 한다.r   r   r   r   r   Nr   r   )zD%(py4)s
{%(py4)s = %(py2)s
{%(py2)s = %(py0)s.strip
}()
} == %(py7)srY  rZ  r[  )r   r   r  r   r   r4   r5   r2   r6   r7   r9   r:   stripr3   )
r<   r   r   r   r   r>   r   r   r   r\  s
             r   1test_empty_html_to_llm_input_returns_empty_stringz?TestEdgeCases.test_empty_html_to_llm_input_returns_empty_string@  s!   "$%%j1&#&&&&&&&&z&&&z&&&&&&&&&&&&&&&&&#&&&#&&&&&&&&&&||#|~##~####~######v###v###|###~##########r   c                    t        d      }|j                  }d}||u }|st        j                  d|fd||f      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}}y)	uM   proxy_list=None이면 InsuranceCrawler.proxy_rotator는 None이어야 한다.Nr   rw   )z5%(py2)s
{%(py2)s = %(py0)s.proxy_rotator
} is %(py5)sr   r   r   r   )
r   r   r2   r3   r4   r5   r6   r7   r9   r:   r<   r   r>   rK   r   r@   r   s          r   2test_none_proxy_list_crawler_proxy_rotator_is_nonez@TestEdgeCases.test_none_proxy_list_crawler_proxy_rotator_is_noneG  s~    "d3$$,,$,,,,$,,,,,,w,,,w,,,$,,,,,,,,,,r   c                    t        d      }|j                  }d}||u }|st        j                  d|fd||f      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}}y	)
uE   adaptive=False이면 InsuranceCrawler.adaptive가 False여야 한다.Fr  rw   )z0%(py2)s
{%(py2)s = %(py0)s.adaptive
} is %(py5)sr   r   r   r   N)
r   r   r2   r3   r4   r5   r6   r7   r9   r:   r  s          r   +test_adaptive_false_disables_smart_matchingz9TestEdgeCases.test_adaptive_false_disables_smart_matchingL  s~    "E2(5(5((((5((((((w(((w((((((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(  }	|	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}	}yc c}w )uK   빈 HTML에서 Spider.parse()는 아이템을 yield하지 않아야 한다.r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   N)r   r   r  r   r   r'   r   r   r   r2   r3   r4   r5   r6   r7   r9   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':;;5zQzQzQss55zQ <r  c                    t        d      }|j                  t              }|j                  |dddi      }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)um   존재하지 않는 CSS 셀렉터로 extract_with_selector() 호출 시 빈 리스트를 반환해야 한다.Fr  z.does-not-existr   r   r  r   rG  r   r.   rC   r1   N)r   r#   r   r  r2   r3   r4   r5   r6   r7   r9   r:   r  s           r   0test_nonexistent_css_selector_returns_empty_listz>TestEdgeCases.test_nonexistent_css_selector_returns_empty_list]  s    "E2}}]+..*G$ / 

 v|vvvr   c                    t        d      }|j                  t              }|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	)
uW   테이블이 없는 HTML에서 extract_table()은 빈 리스트를 반환해야 한다.Fr  r   rG  r   r.   rC   r1   N)r   r#   HTML_SIMPLEr"  r2   r3   r4   r5   r6   r7   r9   r:   r  s           r    test_no_table_returns_empty_listz.TestEdgeCases.test_no_table_returns_empty_listh  s    "E2}}[)&&t,v|vvvr   c                    t               }|j                  d      }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)	uN   빈 ResponseHistory에서 get_chain()은 빈 리스트를 반환해야 한다.r   r   rG  r   r.   rC   r1   N)
r   r;  r2   r3   r4   r5   r6   r7   r9   r:   )r<   r   r   r=   r>   r?   r@   s          r   3test_response_history_empty_get_chain_returns_emptyzATestEdgeCases.test_response_history_empty_get_chain_returns_emptyo  ss    !#""#89v|vvvr   c                    t               }|j                  dd       |j                          t        j                         5 }t        |      dz  }|j                  |       |j                  } |       }|sddt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      dz  }t        t        j                  |            dx}}t        j                   |j#                  d	            }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}}ddd       y# 1 sw Y   yxY w)uN   clear() 후 save()하면 빈 배열([]) JSON 파일이 생성되어야 한다.r   r   z
empty.jsonr   r6  r   Nr7  r8  r   rG  r:  r.   rC   r1   )r   r   clearr   r   r   r   r   r4   r5   r2   r6   r7   r9   r:   r<  r=  r>  r3   )r<   r   r   r6  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;; ;= =     4   4   ;   =      ::dnngn>?D42:42442	 	 	s   FGG$c                    d}t         |u}|st        j                  d|fdt         |f      dt        j                         v st        j
                  t               rt        j                  t               ndt        j                  |      dz  }dd|iz  }t        t        j                  |            dx}}t         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}y)uB   browser.generate_stealth_headers()는 dict를 반환해야 한다.Nr+   r-   r   r.   rC   r1   r   r   r   r   r   )r   r2   r3   r4   r5   r6   r7   r9   r:   rS   r   r   )r<   r=   r>   r?   r@   r   r   r   s           r   *test_generate_stealth_headers_returns_dictz8TestEdgeCases.test_generate_stealth_headers_returns_dict  s    #''|4''''|4''''''|'''|'''4'''''''668&$''''''''z'''z''''''&'''&''''''$'''$''''''''''r   c                     d}t         |u}|st        j                  d|fdt         |f      dt        j                         v st        j
                  t               rt        j                  t               ndt        j                  |      dz  }dd|iz  }t        t        j                  |            dx}}t         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}||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)uL   browser.get_google_referer()는 Google URL 문자열을 반환해야 한다.Nr+   r-   r   r.   rC   r1   r   r   r   r   r   z
google.comr   r   r   )r   r2   r3   r4   r5   r6   r7   r9   r:   rV   r   r   )	r<   r=   r>   r?   r@   r   r   r   r   s	            r   *test_get_google_referer_returns_google_urlz8TestEdgeCases.test_get_google_referer_returns_google_url  sJ   #''|4''''|4''''''|'''|'''4'''''''002&#&&&&&&&&z&&&z&&&&&&&&&&&&&&&&&#&&&#&&&&&&&&&&%|v%%%%|v%%%|%%%%%%v%%%v%%%%%%%r   c                 8   d}t         |u}|st        j                  d|fdt         |f      dt        j                         v st        j
                  t               rt        j                  t               ndt        j                  |      dz  }dd|iz  }t        t        j                  |            dx}}t         j                         }t        |      }|sd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                  |      dz  }t        t        j                  |            d}y)uT   browser.create_resource_blocker()는 호출 가능한 함수를 반환해야 한다.Nr+   r-   r   r.   rC   r1   rb   rR   handlerrc   rY   )r<   r=   r>   r?   r@   r  s         r   -test_create_resource_blocker_returns_callablez;TestEdgeCases.test_create_resource_blocker_returns_callable  s    #''|4''''|4''''''|'''|'''4'''''''668        x   x                   r   c                    t        d      }|j                  t              }|j                  |d      }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)ul   존재하지 않는 reference_selector로 extract_similar() 호출 시 빈 리스트를 반환해야 한다.Fr  z.nonexistent-ref)rm  r   rG  r   r.   rC   r1   N)r   r#   r   extract_similarr2   r3   r4   r5   r6   r7   r9   r:   r  s           r   8test_extract_similar_nonexistent_reference_returns_emptyzFTestEdgeCases.test_extract_similar_nonexistent_reference_returns_empty  s    "E2}}]+((BT(Uv|vvvr   c                    t        g       }|j                  } |       }d}||u }|st        j                  d|fd||f      dt	        j
                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      t        j                  |      dz  }dd|iz  }t        t        j                  |            dx}x}x}}y)uT   빈 proxy_list로 ProxyRotator 생성 시 get_next()는 None을 반환해야 한다.Nrw   )zG%(py4)s
{%(py4)s = %(py2)s
{%(py2)s = %(py0)s.get_next
}()
} is %(py7)srotatorrY  rZ  r[  )
r	   rJ  r2   r3   r4   r5   r6   r7   r9   r:   )r<   r  r>   r   r   r   r   r\  s           r   *test_proxy_rotator_empty_list_returns_nonez8TestEdgeCases.test_proxy_rotator_empty_list_returns_none  s    r")!)T)!T))))!T))))))w)))w))))))!)))T)))))))r   c                    t        dgd      }t        t              }t        j                  t        ||            }|D cg c]  }t        |t              s| }}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}}yc c}w )uR   extraction_config=None이면 parse()는 아이템을 yield하지 않아야 한다.r   Nr   r   r   r   r   r   r   r   r   r   )r<   r    r!   r%   r  r   r=   r   rK   r   r   s              r   1test_spider_extraction_config_none_yields_nothingz?TestEdgeCases.test_spider_extraction_config_none_yields_nothing  s     -."
 '}5++4VXFG#;qz!T':;;5zQzQzQss55zQ <s   E.E.r   )r   r   r   r   r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r   r   r   r  r  +  sU    h	$-
)

	
(&!*
	r   r  )r   r   )?r   builtinsr4   _pytest.assertion.rewrite	assertionrewriter2   r   importlib.util	importlibr<  sysr   pathlibr   typingr   r   pytest__file__resolveparent
_WORKSPACEr6  insertr   r   __annotations__utilspec_from_file_location_specloadermodule_from_specexec_module	Exceptionr   insurance_crawlerr   insurance_spiderr   r	   r
   r   r   r   r   r   r   r   r   r  HTML_WITH_SCRIPTr  r  intr   r  r'   r)   r   r  rU  r  r   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,F
 
FF