
    (<id                        U d Z ddlZddlmZ ddlmZ ddlmZ ddlm	Z	m
Z
 ddlZej                  j                  d e ee      j                   j                                dZee   ed<   	 ddlZedu Zej,                  j/                  ed	      Zd
ZdZdZdZdZdZdZ G d d      Z  G d d      Z! G d d      Z" G d d      Z# G d d      Z$ G d d      Z% G d d      Z& G d d       Z'y# e$ r Y w xY w)!u  
TDD RED 단계: test_insurance_crawler.py

보험사 공개 데이터 크롤러 InsuranceCrawler 클래스에 대한 테스트 스위트.
(insurance_crawler.py는 아직 구현되지 않음 - TDD RED 단계)

주의:
- 모든 테스트는 로컬 HTML 문자열로 수행. 외부 네트워크 호출 없음.
- 실제 보험사 사이트 크롤링은 robots.txt를 반드시 확인하고 준수해야 합니다.
- 합법적 공개 데이터만 대상으로 합니다.
    N)Path)
ModuleType)Optional)	MagicMockpatch_insurance_crawleruI   insurance_crawler.py 미구현 (TDD RED 단계 - 토르가 구현 예정))reasonuv  
<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>
</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>
  <table>
    <tr><td>화재보험</td><td>50000</td></tr>
    <tr><td>자동차보험</td><td>100000</td></tr>
  </table>
</body>
</html>
z1
<html>
<body>
  <table></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> c                   X    e Zd ZdZedd       Zedd       Zedd       Zedd       Zy)TestInsuranceCrawlerInitu%   InsuranceCrawler 초기화 테스트.Nc                 \    t         J t        j                         }|j                  du sJ y)u=   기본 생성 시 adaptive=True로 초기화되어야 한다.NTr   InsuranceCrawleradaptiveselfcrawlers     U/home/jay/workspace/.worktrees/task-2057-dev2/scripts/tests/test_insurance_crawler.pytest_default_init_adaptive_truez8TestInsuranceCrawlerInit.test_default_init_adaptive_truer   s2     "---$5574'''    c                     t         J ddlm} ddg}t        j                  |      }|j                  J t        |j                  |      sJ y)uS   프록시 리스트 전달 시 ProxyRotator 인스턴스가 생성되어야 한다.Nr   )ProxyRotatorzhttp://proxy1:8080zhttp://proxy2:8080)
proxy_list)r   crawl_utilsr   r   proxy_rotator
isinstance)r   r   r   r   s       r   test_init_with_proxy_listz2TestInsuranceCrawlerInit.test_init_with_proxy_listy   sV     "---,*,@A
$55L$$000'//>>>r   c                 X    t         J t        j                         }|j                  J y)u:   프록시 없으면 proxy_rotator는 None이어야 한다.N)r   r   r   r   s     r   test_init_without_proxyz0TestInsuranceCrawlerInit.test_init_without_proxy   s0     "---$557$$,,,r   c                 `    t         J t        j                  d      }|j                  du sJ y)u@   adaptive=False 전달 시 adaptive 속성이 False여야 한다.NFr   r   r   s     r   test_init_adaptive_falsez1TestInsuranceCrawlerInit.test_init_adaptive_false   s4     "---$55uE5(((r   returnN)	__name__
__module____qualname____doc___skip_if_missingr   r   r   r"    r   r   r   r   o   sS    /( ( ? ? - - ) )r   r   c                   X    e Zd ZdZedd       Zedd       Zedd       Zedd       Zy)TestInsuranceCrawlerParseu-   InsuranceCrawler.parse() 메서드 테스트.Nc                     t         J ddlm} t        j                         }|j	                  t
              }t        ||      sJ y)u8   parse()가 Selector 인스턴스를 반환해야 한다.Nr   Selector)r   scrapling.parserr/   r   parseHTML_SIMPLEr   r   r/   r   results       r   test_parse_returns_selectorz5TestInsuranceCrawlerParse.test_parse_returns_selector   s?     "----$557{+&(+++r   c                     t         J ddlm} t        j                         }d}|j	                  t
        |      }t        ||      sJ |j                  |k(  sJ y)u1   url 인자가 Selector에 전달되어야 한다.Nr   r.   zhttps://example-insurance.co.krurl)r   r0   r/   r   r1   r2   r   r8   )r   r/   r   r8   r4   s        r   test_parse_with_urlz-TestInsuranceCrawlerParse.test_parse_with_url   sZ     "----$557/{4&(+++zzS   r   c                     t         J ddlm} t        j                  d      }|j	                  t
              }t        ||      sJ |j                  du sJ y)uc   adaptive=True인 crawler로 parse()하면 Selector가 adaptive 설정으로 생성되어야 한다.Nr   r.   Tr!   r   r0   r/   r   r1   r2   r   _Selector__adaptive_enabledr3   s       r   test_parse_adaptive_modez2TestInsuranceCrawlerParse.test_parse_adaptive_mode   sU     "----$55tD{+&(+++11T999r   c                     t         J ddlm} t        j                  d      }|j	                  t
              }t        ||      sJ |j                  du sJ y)uV   adaptive=False인 crawler로 parse()하면 Selector의 adaptive가 False여야 한다.Nr   r.   Fr!   r;   r3   s       r   test_parse_non_adaptive_modez6TestInsuranceCrawlerParse.test_parse_non_adaptive_mode   sU     "----$55uE{+&(+++11U:::r   r#   )	r%   r&   r'   r(   r)   r5   r9   r=   r?   r*   r   r   r,   r,      sS    7, , 
! 
! 	: 	: ; ;r   r,   c                   |    e Zd ZdZed	d       Zed	d       Zed	d       Zed	d       Zed	d       Z	ed	d       Z
y)
TestExtractWithSelectoru=   InsuranceCrawler.extract_with_selector() 메서드 테스트.Nc                 D   t         J t        j                  d      }|j                  t              }|j	                  |dddd      }t        |t              sJ t        |      d	k(  sJ |d
   d   dk(  sJ |d
   d   dk(  sJ |d   d   dk(  sJ |d   d   dk(  sJ y)uH   CSS 셀렉터로 name, price 필드를 올바르게 추출해야 한다.NFr!   .product.name.pricenamepricecss_selectorfields   r   rG      화재보험rH   50000      자동차보험      생명보험r   r   r1   HTML_PRODUCTSextract_with_selectorr   listlenr   r   pager4   s       r   test_extract_basic_fieldsz1TestExtractWithSelector.test_extract_basic_fields   s     "---$55uE}}]+..##h7 / 

 &$'''6{aay N222ay!W,,,ay $5555ay N222r   c                     t         J t        j                  d      }|j                  t              }|j	                  |dddi      }|g k(  sJ y)uG   매칭 없는 CSS 셀렉터이면 빈 리스트를 반환해야 한다.NFr!   .nonexistentrG   rD   rI   )r   r   r1   rT   rU   rX   s       r   test_extract_empty_selectorz3TestExtractWithSelector.test_extract_empty_selector   s_     "---$55uE}}]+..'G$ / 

 ||r   c                     t         J t        j                  d      }|j                  t              }|j	                  |dd      }t        |t              sJ t        |      dk(  sJ |D ]  }d|v rJ  y)uQ   fields=None이면 컨테이너의 텍스트를 'text' 키로 추출해야 한다.NFr!   rC   rI   rL   textrS   r   r   rY   r4   items        r   test_extract_no_fieldsz.TestExtractWithSelector.test_extract_no_fields   s     "---$55uE}}]+..# / 

 &$'''6{a 	"DT>!>	"r   c                     t         J t        j                  d      }|j                  t              }|j	                  |ddddi      }t        |t              sJ t        |      d	k(  sJ y)
ub   identifier 전달 시 예외 없이 실행되고 결과를 반환해야 한다 (auto_save 동작).NFr!   rC   test_product_listrG   rD   rJ   
identifierrK   rL   rS   rX   s       r   test_extract_with_identifierz4TestExtractWithSelector.test_extract_with_identifier  sx     "---$55uE}}]+..#*G$	 / 
 &$'''6{ar   c                     t         J t        j                  d      }|j                  t              }|j	                  |dddd      }t        |      d	k(  sJ |d
   d   dk(  sJ |d
   d   J y)uU   일부 필드 서브셀렉터가 없으면 해당 값은 None으로 채워야 한다.NFr!   rC   rD   z.nonexistent-category)rG   categoryrI   rL   r   rG   rM   ri   )r   r   r1   rT   rU   rW   rX   s       r   test_extract_partial_fieldsz3TestExtractWithSelector.test_extract_partial_fields  s     "---$55uE}}]+..##1HI / 

 6{aay N222ay$,,,r   c                     t         J t        j                  d      }|j                  t              }|j	                  |dddi      }t        |t              sJ |D ]  }t        |t              rJ  y)u/   반환값은 list[dict] 타입이어야 한다.NFr!   rC   rG   rD   rI   )r   r   r1   rT   rU   r   rV   dictr`   s        r   "test_extract_returns_list_of_dictsz:TestExtractWithSelector.test_extract_returns_list_of_dicts!  s     "---$55uE}}]+..#G$ / 

 &$''' 	*DdD)))	*r   r#   )r%   r&   r'   r(   r)   rZ   r]   rb   rg   rj   rm   r*   r   r   rA   rA      s{    G3 3" 
 
 " "      - - * *r   rA   c                   X    e Zd ZdZedd       Zedd       Zedd       Zedd       Zy)TestExtractSimilaru7   InsuranceCrawler.extract_similar() 메서드 테스트.Nc                     t         J t        j                  d      }|j                  t              }|j	                  |d      }t        |t              sJ t        |      dk\  sJ y)uJ   유사 구조 요소들을 모두 찾아 리스트를 반환해야 한다.NFr!   rC   reference_selectorrO   )r   r   r1   rT   extract_similarr   rV   rW   rX   s       r   test_find_similar_basicz*TestExtractSimilar.test_find_similar_basic9  sm     "---$55uE}}]+(() ) 
 &$'''6{ar   c                     t         J t        j                  d      }|j                  t              }|j	                  |dddd      }t        |t              sJ |D ]  }d	|v sJ d
|v rJ  y)uI   필드 매핑 적용 시 각 항목에 필드 키가 존재해야 한다.NFr!   rC   rD   rE   rF   )rr   rK   rG   rH   r   r   r1   rT   rs   r   rV   r`   s        r   test_find_similar_with_fieldsz0TestExtractSimilar.test_find_similar_with_fieldsG  s     "---$55uE}}]+(()#h7 ) 

 &$''' 	#DT>!>d?"?	#r   c                     t         J t        j                  d      }|j                  t              }|j	                  |d      }|g k(  sJ y)uP   기준 셀렉터가 매칭되지 않으면 빈 리스트를 반환해야 한다.NFr!   z.nonexistent-elementrq   )r   r   r1   rT   rs   rX   s       r   test_find_similar_no_matchz-TestExtractSimilar.test_find_similar_no_matchW  sW     "---$55uE}}]+((5 ) 
 ||r   c                    t         J t        j                  d      }|j                  t              }|j	                  |dd      }t        |t              sJ |j	                  |dd      }t        |t              sJ y)uH   임계값 인자가 정상적으로 전달되어 실행되어야 한다.NFr!   rC   g        )rr   	thresholdg?rv   )r   r   rY   result_looseresult_stricts        r   test_find_similar_thresholdz.TestExtractSimilar.test_find_similar_thresholdc  s     "---$55uE}}]+..) / 

 ,---//) 0 

 -...r   r#   )	r%   r&   r'   r(   r)   rt   rw   ry   r~   r*   r   r   ro   ro   6  sS    A    # # 	 	 / /r   ro   c                   j    e Zd ZdZedd       Zedd       Zedd       Zedd       Zedd       Z	y)	TestExtractTableu5   InsuranceCrawler.extract_table() 메서드 테스트.Nc                 8   t         J t        j                  d      }|j                  t              }|j	                  |      }t        |t              sJ t        |      dk(  sJ |d   d   dk(  sJ |d   d   d	k(  sJ |d   d
   dk(  sJ |d   d   dk(  sJ y)u\   기본 테이블 추출: th 헤더 + td 데이터가 딕셔너리로 변환되어야 한다.NFr!   rQ   r   u	   보험명rM   u	   보험료rN   u   가입기간u   1년rO   rP   )r   r   r1   
HTML_TABLEextract_tabler   rV   rW   rX   s       r   test_extract_table_basicz)TestExtractTable.test_extract_table_basic  s     "---$55uE}}Z(&&t,&$'''6{aay%777ay%000ay(F222ay%)::::r   c                 (   t         J t        j                  d      }|j                  t              }|j	                  |      }t        |t              sJ t        |      dk(  sJ d|d   v sJ d|d   v sJ |d   d   dk(  sJ |d   d   d	k(  sJ y)
uJ   헤더(th) 없으면 col_0, col_1... 형태로 자동 명명해야 한다.NFr!   rQ   col_0r   col_1rM   rN   )r   r   r1   HTML_TABLE_NO_HEADERr   r   rV   rW   rX   s       r   test_extract_table_no_headerz-TestExtractTable.test_extract_table_no_header  s     "---$55uE}}12&&t,&$'''6{a&)###&)###ay!^333ay!W,,,r   c                     t         J t        j                  d      }|j                  t              }|j	                  |      }|g k(  sJ y)u9   빈 테이블이면 빈 리스트를 반환해야 한다.NFr!   )r   r   r1   HTML_EMPTY_TABLEr   rX   s       r   test_extract_table_emptyz)TestExtractTable.test_extract_table_empty  sK     "---$55uE}}-.&&t,||r   c                    t         J d}t        j                  d      }|j                  |      }|j                  |d      }t	        |t
              sJ t        |      dk(  sJ |d   d	   d
k(  sJ |d   d   dk(  sJ y)u\   커스텀 table_selector를 사용하여 특정 테이블을 선택할 수 있어야 한다.Nu   <html><body>
        <table id="insurance-table">
          <tr><th>상품명</th><th>월납입료</th></tr>
          <tr><td>종신보험</td><td>200000</td></tr>
        </table>
        </body></html>Fr!   z#insurance-table)table_selectorrO   r   u	   상품명u   종신보험u   월납입료200000)r   r   r1   r   r   rV   rW   )r   htmlr   rY   r4   s        r   #test_extract_table_custom_selectorsz4TestExtractTable.test_extract_table_custom_selectors  s     "--- %55uE}}T"&&t<N&O&$'''6{aay%777ay(H444r   c                     t         J t        j                  d      }|j                  t              }|j	                  |      }|g k(  sJ y)u\   테이블이 없는 HTML에서 extract_table() 호출 시 빈 리스트 반환해야 한다.NFr!   )r   r   r1   r2   r   rX   s       r   )test_extract_table_no_table_returns_emptyz:TestExtractTable.test_extract_table_no_table_returns_empty  sJ     "---$55uE}}[)&&t,||r   r#   )
r%   r&   r'   r(   r)   r   r   r   r   r   r*   r   r   r   r   ~  sg    ?; ; - -   5 5"  r   r   c                   j    e Zd ZdZedd       Zedd       Zedd       Zedd       Zedd       Z	y)	TestToLlmInputu4   InsuranceCrawler.to_llm_input() 메서드 테스트.Nc                     t         J t        j                         }|j                  d      }t        |t              sJ d|v sJ y)uI   HTML을 마크다운으로 변환하고 문자열을 반환해야 한다.Nu   <p>보험 공시 정보</p>   보험 공시 정보)r   r   to_llm_inputr   strr   r   r4   s      r   test_to_llm_input_basicz&TestToLlmInput.test_to_llm_input_basic  sM     "---$557%%&CD&#&&&%///r   c                 ~    t         J t        j                         }|j                  t              }d|vsJ d|vsJ y)uE   script 태그가 제거되어야 한다 (clean_html 파이프라인).Nalertxssr   r   r   HTML_WITH_SCRIPTr   s      r    test_to_llm_input_removes_scriptz/TestToLlmInput.test_to_llm_input_removes_script  sK     "---$557%%&67f$$$F"""r   c                 r    t         J t        j                         }|j                  t              }d|v sJ y)u5   텍스트 내용은 결과에 보존되어야 한다.Nr   r   r   s      r    test_to_llm_input_preserves_textz/TestToLlmInput.test_to_llm_input_preserves_text  s=     "---$557%%&67%///r   c                     t         J t        j                         }|j                  t              }t	        |t
              sJ |j                         dk(  sJ y)uN   빈 HTML 입력 시 빈 문자열 또는 최소한 str을 반환해야 한다.Nr
   )r   r   r   
HTML_EMPTYr   r   stripr   s      r   test_to_llm_input_emptyz&TestToLlmInput.test_to_llm_input_empty  sR     "---$557%%j1&#&&&||~###r   c                     t         J t        j                         }|j                  t              }t	        |t
              sJ y)u2   반환값은 반드시 str 타입이어야 한다.N)r   r   r   r2   r   r   r   s      r    test_to_llm_input_returns_stringz/TestToLlmInput.test_to_llm_input_returns_string  s>     "---$557%%k2&#&&&r   r#   )
r%   r&   r'   r(   r)   r   r   r   r   r   r*   r   r   r   r     sg    >0 0 # # 0 0 $ $ ' 'r   r   c                   F    e Zd ZdZedd       Zedd       Zedd       Zy)TestExtractFieldsuE   InsuranceCrawler._extract_fields() 내부 헬퍼 메서드 테스트.Nc                 ,   t         J ddlm} t        j                  d      } |t        d      }|j                  d      }|j                  |ddd	      }t        |t              sJ t        |      d
k(  sJ |d   d   dk(  sJ |d   d   dk(  sJ y)u^   기본 필드 추출: elements에서 fields 매핑에 따라 데이터를 추출해야 한다.Nr   r.   Fr!   rC   rD   rE   rF   rL   rG   rM   rH   rN   )
r   r0   r/   r   rT   css_extract_fieldsr   rV   rW   r   r/   r   rY   elementsr4   s         r   test_extract_fields_basicz+TestExtractFields.test_extract_fields_basic  s     "----$55uE688J'((Gh3WX&$'''6{aay N222ay!W,,,r   c                    t         J ddlm} t        j                  d      } |t        d      }|j                  d      }|j                  |ddd	      }t        |      d
k(  sJ |d   d   dk(  sJ |d   d   J y)uT   서브셀렉터가 존재하지 않으면 해당 필드 값은 None이어야 한다.Nr   r.   Fr!   rC   rD   r\   )rG   discountrL   rG   rM   r   r   r0   r/   r   rT   r   r   rW   r   s         r   (test_extract_fields_missing_sub_selectorz:TestExtractFields.test_extract_fields_missing_sub_selector  s     "----$55uE688J'((GQ_3`a6{aay N222ay$,,,r   c                 $   t         J ddlm} t        j                  d      } |t        d      }|j                  d      }|j                  |ddi      }t        |      d	k(  sJ |D cg c]  }|d   	 }}d
|v sJ d|v sJ d|v sJ yc c}w )uN   복수 요소에서 일괄 추출 시 모든 요소가 처리되어야 한다.Nr   r.   Fr!   rC   rG   rD   rL   rM   rP   rR   r   )r   r/   r   rY   r   r4   ra   namess           r   %test_extract_fields_multiple_elementsz7TestExtractFields.test_extract_fields_multiple_elements  s     "----$55uE688J'((FG3DE6{a*01$f11&&& E)))&&& 2s   ,Br#   )r%   r&   r'   r(   r)   r   r   r   r*   r   r   r   r     s?    O- - - - ' 'r   r   c                   4    e Zd ZdZedd       Zedd       Zy)TestSmartMatchingIntegrationuA   Smart Matching 통합 테스트 (auto_save + adaptive 재탐색).Nc                 <   t         J t        j                  d      }|j                  t        d      }|j	                  |dddd	d
      }t        |      dk(  sJ d}|j                  |d      }|j	                  |dddd	d
      }t        |t              sJ y)u  auto_save로 저장 후 구조 변경된 HTML에서 adaptive로 재탐색해야 한다.

        시나리오:
        1. 원본 HTML로 extract_with_selector() (auto_save=True, identifier 포함)
        2. 구조 변경된 HTML로 adaptive 모드 재탐색
        3. 결과가 리스트 형태로 반환되는지 확인 (adaptive 재탐색 성공 여부는 환경 의존)
        NTr!   z(https://example-insurance.co.kr/productsr7   rC   insurance_product_adaptive_testrD   rE   rF   re   rL   u   
        <html>
        <body>
          <article class="insurance-item">
            <h3 class="name">화재보험</h3>
            <span class="price">50000</span>
          </article>
        </body>
        </html>
        )r   r   r1   rT   rU   rW   r   rV   )r   r   page_originalresult1html_changedpage_changedresult2s          r   test_auto_save_and_adaptivez8TestSmartMatchingIntegration.test_auto_save_and_adaptive6  s     "---$55tD  m9cd//#8#h7	 0 
 7|q   	 }}\7a}b//#8#h7	 0 
 '4(((r   c                     t         J t        j                  d      }|j                  t        d      }|j	                  |dddd	i
      }t        |t              sJ t        |      dk(  sJ y)uc   adaptive=True 크롤러는 extract_with_selector()에서 adaptive 옵션을 활성화해야 한다.NTr!   zhttps://example.comr7   rC   adaptive_testrG   rD   re   rL   rS   rX   s       r   +test_extract_with_selector_adaptive_enabledzHTestSmartMatchingIntegration.test_extract_with_selector_adaptive_enabledb  s}     "---$55tD}}]0E}F..#&G$	 / 
 &$'''6{ar   r#   )r%   r&   r'   r(   r)   r   r   r*   r   r   r   r   3  s,    K)) ))V    r   r   )(r(   syspathlibr   typesr   typingr   unittest.mockr   r   pytestpathinsertr   __file__parentr   __annotations__insurance_crawlerModuleNotFoundError_MISSINGmarkskipifr)   rT   r   r   r   r   r2   r   r   r,   rA   ro   r   r   r   r   r*   r   r   <module>r      s1  
     *  3tH~,,334 5 ,0 HZ( /	2 %;;%%V &  &

	    ?
!) !)R/; /;n_* _*N@/ @/PA AR-' -'j/' /'n=  = o  		s   -C1 1C98C9