
    >ih                     6   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mZ ddlmZmZmZ ddlZddlZ ee	j&                  j)                  dd            Z ee      e
j.                  vr"e
j.                  j1                  d ee             dZedz  ed<   	 ddlmZmZmZm Z  ejH                  jK                  edude       Z&d2dedefdZ'd3dedz  defdZ(d3dedz  defdZ)d3dedz  defdZ*d3dedz  defdZ+dedefdZ,ejZ                  d        Z.ejZ                  d        Z/e&d        Z0e&d        Z1ejH                  jd                  e&d               Z3ejH                  jd                  e&d               Z4ejH                  jd                  e&d                Z5ejH                  jd                  e&d!               Z6ejH                  jd                  e&d"               Z7ejH                  jd                  e&d#               Z8ejH                  jd                  e&d$               Z9ejH                  jd                  e&d%               Z:ejH                  jd                  e&d&               Z;ejH                  jd                  e&d'               Z<ejH                  jd                  e&d(               Z=ejH                  jd                  e&d)               Z>e&d*        Z?dZ@edz  ed+<   dZAdZB	 dd,lCmDZDmEZE eDZAeEZBejH                  jK                  e@dud-e@       ZGeGd.        ZHeGd/        ZIeGd0        ZJejH                  jd                  eGe&d1                      ZKy# e$ r0Z!e!ZdZ  e"d	e#fi       Z e"d
e#fi       Z e"de#fi       ZY dZ![!dZ![!ww xY w# e$ rZFeFZ@Y dZF[FdZF[Fww xY w)4u  
test_tistory_publisher.py

TistoryPublisher 단위 테스트 (아르고스 작성)

- Playwright 기반 티스토리 자동화 퍼블리셔의 mock 단위 테스트
- 모든 외부 의존성(Playwright, 브라우저)은 AsyncMock/MagicMock으로 대체
- 실제 브라우저 실행 없음

TistoryPublisher._ensure_browser() 구현 패턴:
  pw = await async_playwright().start()   ← async_playwright()는 동기 호출, .start()가 코루틴
  self._browser = await pw.chromium.launch(...)
  self._context = await self._browser.new_context(...)
  self._page    = await self._context.new_page()

테스트 항목:
  1. test_init_from_env:               환경변수에서 설정 로드
  2. test_init_from_args:              생성자 인자로 설정
  3. test_load_session_file_not_found: 세션 파일 없을 때 FileNotFoundError
  4. test_load_session_success:        유효한 세션 파일 로드
  5. test_check_session_valid_true:    세션 유효 시 True 반환
  6. test_check_session_valid_expired: 세션 만료(로그인 리다이렉트) 시 False 반환
  7. test_publish_post_private:        비공개 글 발행 성공
  8. test_publish_post_with_tags:      태그 포함 발행
  9. test_publish_post_session_expired: 세션 만료 시 SessionExpiredError
 10. test_publish_post_failure:        발행 실패 시 PublishError
 11. test_edit_post_success:           글 수정 성공
 12. test_get_post_status:             글 상태 확인
 13. test_upload_image_success:        이미지 업로드 성공
 14. test_close:                       리소스 정리 확인
 15. test_visibility_default_private:  visibility 기본값 private 확인
 16. test_pipeline_parse_html_title:   HTML에서 제목 추출
 17. test_pipeline_parse_html_tags:    HTML meta에서 태그 추출
 18. test_pipeline_file_not_found:     HTML 파일 없을 때 에러
 19. test_pipeline_publish_flow:       전체 파이프라인 흐름 (mock)
    N)Path)	AsyncMock	MagicMockpatchWORKSPACE_ROOTz/home/jay/workspace_IMPORT_ERROR)LoginRequiredErrorPublishErrorSessionExpiredErrorTistoryPublisherr   r
   r	      TistoryPublisher 미구현: )reasonurlreturnc                    t               }t               |_        t               |_        t               |_        t               |_        t               |_        t               |_        t        d      |_        t               |_	        t               |_
        t        t        t        d                  |_        t               |_        t        | fd      t        |      _        |S )uA   Playwright Page 객체를 흉내내는 MagicMock을 반환한다.Nreturn_value )
inner_textc                     |S N )self_us     3/home/jay/workspace/tests/test_tistory_publisher.py<lambda>z!_make_mock_page.<locals>.<lambda>p   s    2     )r   r   gotofillclickwait_for_selectorwait_for_load_statewait_for_urlevaluate
screenshotcloselocatorexpect_responsepropertytyper   )r   pages     r   _make_mock_pager,   `   s    ;DDIDIDJ&[D({D!D40DMkDODJ)yVX?Y*Z[DL$;D c56DJNKr   r+   c                     t               }| xs
 t               }t        |      |_        t               |_        t        dddg      |_        t        g g d      |_        t               |_        |S )uK   Playwright BrowserContext 객체를 흉내내는 MagicMock을 반환한다.r   sessabc)namevaluecookiesorigins)r   r,   r   new_pageadd_cookiesr3   storage_stater&   )r+   ctx_pages      r   _make_mock_contextr:   u   s_    
+C%O%E%0CLkCO6E*J)KLCK!2"/MNCCIJr   contextc                 v    t               }| xs
 t               }t        |      |_        t               |_        |S )uD   Playwright Browser 객체를 흉내내는 MagicMock을 반환한다.r   )r   r:   r   new_contextr&   )r;   browser_ctxs      r   _make_mock_browserr@      s2    kG*(*D#6GKGMNr   r>   c                     t               }| xs
 t               }t               |_        t        |      |j                  _        t               |_        |S )uP   async_playwright().start() 반환값인 Playwright 인스턴스를 흉내낸다.r   )r   r@   chromiumr   launchstop)r>   pw_inst_browsers      r   _make_pw_instancerG      sB    kG.,.H {G'X>G;GLNr   c                 ^    t        |       }t        t        t        |                  }|S )ue  
    scripts.blog.tistory_publisher.async_playwright 를 대체할 callable을 반환한다.

    _ensure_browser()가 사용하는 패턴:
        pw = await async_playwright().start()

    따라서 async_playwright()는 동기적으로 .start() 메서드를 가진 객체를 반환해야 하고,
    .start()는 코루틴(AsyncMock)이어야 한다.
    r>   r   )start)rG   r   r   )r>   rE   mock_callables      r   _make_async_playwright_callablerL      s)      0G99RY;Z+[\Mr   tmp_pathc                 l    | dz  }ddddgg d}|j                  t        j                  |             |S )Nzsession.json	TSSESSIONzmock-session-valuez.tistory.com)r0   r1   domainr2   )
write_textjsondumps)rM   session_filesession_datas      r   _write_sessionrV      sD    n,L(3GSabcL DJJ|45r   c                      t               S )u2   async_playwright를 대체하는 callable fixture.)rL   r   r   r   mock_playwright_callablerX      s     +,,r   c                   K   t         t        j                  dt                 t        |       }t	        d|      5  t        dt        |      d      }| ddd       y# 1 sw Y   yxY ww)uH   TistoryPublisher 인스턴스를 mock playwright와 함께 생성한다.Nr   /scripts.blog.tistory_publisher.async_playwrighttestblogT	blog_namesession_pathheadless)r   pytestskiprV   r   r   str)rM   rX   rT   pubs       r   	publisherrd      sk       2=/BC!(+L	@BZ	[  \*

 	  s   :A.A"	A."A+'A.c                    t        |       }|j                  dd       |j                  dt        |             t        t        j
                  d   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                  |      dz  }d	d
|iz  }t        t        j                  |            dx}x}}|j                  }t        |      }	||	k(  }|s7t        j                  d|fd||	f      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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}x}}	y)uM   환경변수에서 blog_name, session_path를 로드할 수 있어야 한다.TISTORY_BLOG_NAMEzenv-blogTISTORY_SESSION_PATHr]   r^   ==z1%(py2)s
{%(py2)s = %(py0)s.blog_name
} == %(py5)src   py0py2py5assert %(py7)spy7NzR%(py2)s
{%(py2)s = %(py0)s.session_path
} == %(py7)s
{%(py7)s = %(py4)s(%(py5)s)
}rb   rT   rm   rn   py4ro   rq   assert %(py9)spy9)rV   setenvrb   r   osenvironr]   
@pytest_ar_call_reprcompare@py_builtinslocals_should_repr_global_name	_safereprAssertionError_format_explanationr^   )rM   monkeypatchrT   rc   @py_assert1@py_assert4@py_assert3@py_format6@py_format8@py_assert6@py_format10s              r   test_init_from_envr      s    "(+L*J7-s</@A
**01ZZ 67C ==&J&=J&&&&=J&&&&&&3&&&3&&&=&&&J&&&&&&&0s<0000000000000030003000000000s000s000000<000<00000000000r   c                    t        |       }t        dt        |      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                  |      dz  }dd	|iz  }t        t	        j                  |            d
x}x}}|j                  }t        |      }||k(  }|s7t	        j
                  d|fd||f      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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}x}}|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
)uX   생성자 인자로 blog_name, session_path, headless를 설정할 수 있어야 한다.zmy-blogFr\   ri   rk   rc   rl   rp   rq   Nrr   rb   rT   rs   ru   rv   is)z0%(py2)s
{%(py2)s = %(py0)s.headless
} is %(py5)s)rV   r   rb   r]   rz   r{   r|   r}   r~   r   r   r   r^   r_   )
rM   rT   rc   r   r   r   r   r   r   r   s
             r   test_init_from_argsr      s    "(+L
&C
 ==%I%=I%%%%=I%%%%%%3%%%3%%%=%%%I%%%%%%%0s<0000000000000030003000000000s000s000000<000<00000000000<< 5 <5    <5      3   3   <   5       r   c                    K   t        | dz        }t        d|      }t        j                  t              5  |j                          d{    ddd       y7 # 1 sw Y   yxY ww)uZ   세션 파일이 없으면 _load_session()이 FileNotFoundError를 발생시켜야 한다.znonexistent_session.jsonr[   rh   N)rb   r   r`   raisesFileNotFoundError_load_session)rM   missing_pathrc   s      r    test_load_session_file_not_foundr      s`      x"<<=L
Zl
KC	(	) "!!!" "!" "s.   5A'AAA	A'AA$ A'c                    K   t        |       }t        dt        |            }|j                          d{    y7 w)uV   유효한 세션 파일이 있으면 _load_session()이 정상 완료되어야 한다.r[   rh   N)rV   r   rb   r   )rM   rT   rc   s      r   test_load_session_successr     s5      "(+L
Zc,>O
PC



s   5?=?c                 r  K   d}t        |      }t        |      }t        |      }t        |      }t	        |       }t        d|      5  t        dt        |            }|j                          d	{   }d	d	d	       d
}	|	u }
|
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	7 # 1 sw Y   xY ww)u^   로그인 상태의 페이지 URL이면 _check_session_valid()가 True를 반환해야 한다.z$https://testblog.tistory.com/manage/r   r+   r;   rI   rZ   r[   rh   NTr   z%(py0)s is %(py3)sresultrm   py3assert %(py5)sro   r,   r:   r@   rL   rV   r   r   rb   _check_session_validrz   r{   r|   r}   r~   r   r   r   )rM   	valid_urlr+   r8   r>   rK   rT   rc   r   @py_assert2r   @py_format4r   s                r   test_check_session_valid_truer     s      7Iy)D
$
'C -G3GDM!(+L	@-	P 2#lBST//112 6T>6T66T 22 21   A
D7*D+6D)7D+;B.D7)D++D40D7c                 r  K   d}t        |      }t        |      }t        |      }t        |      }t	        |       }t        d|      5  t        dt        |            }|j                          d	{   }d	d	d	       d
}	|	u }
|
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	7 # 1 sw Y   xY ww)ue   로그인 페이지로 리다이렉트 되면 _check_session_valid()가 False를 반환해야 한다.z"https://www.tistory.com/auth/loginr   r   r   rI   rZ   r[   rh   NFr   r   r   r   r   ro   r   )rM   	login_urlr+   r8   r>   rK   rT   rc   r   r   r   r   r   s                r    test_check_session_valid_expiredr   %  s      5Iy)D
$
'C -G3GDM!(+L	@-	P 2#lBST//112 6U?6U66U 22 2r   c                   K   d}t        |      }t        |      }t        |      }t        |      }t	        |       }t        d|      5  t        dt        |            }t        d	
      |_	        |j                  ddd       d{   }ddd       d}	|	u}
|
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}
}	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7 # 1 sw Y   xY ww)uS   비공개(private) 글 발행이 성공하면 URL 문자열을 반환해야 한다.zhttps://testblog.tistory.com/42r   r   r   rI   rZ   r[   rh   Tr      테스트 제목u   <p>본문 내용</p>private)titlecontent
visibilityNis notz%(py0)s is not %(py3)spost_urlr   r   ro   5assert %(py4)s
{%(py4)s = %(py0)s(%(py1)s, %(py2)s)
}
isinstancerb   rm   py1rn   rt   r,   r:   r@   rL   rV   r   r   rb   r   r   publish_postrz   r{   r|   r}   r~   r   r   r   r   )rM   published_urlr+   r8   r>   rK   rT   rc   r   r   r   r   r   r   @py_format5s                  r   test_publish_post_privater   =  s     6M}-D
$
'C -G3GDM!(+L	@-	P 	
#lBST#,$#? ))$*  * 
 
	
  8484884h$$$$$$$$:$$$:$$$$$$h$$$h$$$$$$$$$$$$$$$$$$$
	
 	
s1   A
I.?I!II!GI.I!!I+&I.c                   K   d}t        |      }t        |      }t        |      }t        |      }t	        |       }g d}t        d|      5  t        dt        |      	      }t        d
      |_	        |j                  dd|d       d{   }	ddd       d}
	|
u}|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}}
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7 # 1 sw Y   xY ww)uD   태그 목록을 포함하여 글을 발행할 수 있어야 한다.zhttps://testblog.tistory.com/99r   r   r   rI   )python
playwrighttistoryrZ   r[   rh   Tr   u   태그 테스트u   <p>태그 포함 본문</p>r   )r   r   tagsr   Nr   r   r   r   r   ro   r   r   rb   r   r   )rM   r   r+   r8   r>   rK   rT   r   rc   r   r   r   r   r   r   r   s                   r   test_publish_post_with_tagsr   X  s     6M}-D
$
'C -G3GDM!(+L.D	@-	P 	
#lBST#,$#? ))$1 	 * 
 
		
  8484884h$$$$$$$$:$$$:$$$$$$h$$$h$$$$$$$$$$$$$$$$$$$
		
 	
s2   AI3A I&I#I&GI3#I&&I0+I3c                 \  K   t        |       }t               }t        d|      5  t        dt	        |            }t        d      |_        t        j                  t              5  |j                  dd       d	{    d	d	d	       d	d	d	       y	7 # 1 sw Y   xY w# 1 sw Y   y	xY ww)
uc   세션이 만료된 상태에서 발행을 시도하면 SessionExpiredError가 발생해야 한다.rZ   r[   rh   Fr   u	   테스트   <p>본문</p>r   r   N)rV   rL   r   r   rb   r   r   r`   r   r   r   )rM   rT   rK   rc   s       r   !test_publish_post_session_expiredr   t  s      "(+L35M	@-	P O#lBST#,%#@ ]]./ 	O""o"NNN	O	O O
 O	O 	O	O OsG   "B,AB %B<B=BB 		B,BB	B  B)%B,c                   K   t               }t        t        d            |_        t	        |      }t        |      }t        |      }t        |       }t        d|      5  t        dt        |            }t        d	
      |_        t        j                  t              5  |j                  dd       d{    ddd       ddd       y7 # 1 sw Y   xY w# 1 sw Y   yxY ww)uW   발행 중 예기치 않은 오류가 발생하면 PublishError가 발생해야 한다.zSelector timeoutside_effectr   r   rI   rZ   r[   rh   Tr   u   실패 테스트r   r   N)r,   r   	Exceptionr!   r:   r@   rL   rV   r   r   rb   r   r`   r   r
   r   rM   r+   r8   r>   rK   rT   rc   s          r   test_publish_post_failurer     s     
 D&9=O3PQD
$
'C -G3GDM!(+L	@-	P V#lBST#,$#? ]]<( 	V"");_"UUU	V	V V
 V	V 	V	V VsI   A C*"AC#C:C;C?C	C*CC	CC'#C*c                 J  K   t               }t        |      }t        |      }t        |      }t	        |       }t        d|      5  t        dt        |            }t        d      |_	        |j                  d	d
d       d{    ddd       y7 # 1 sw Y   yxY ww)uY   post_id와 수정할 필드를 전달하면 edit_post()가 정상 완료되어야 한다.r   r   rI   rZ   r[   rh   Tr   42u   수정된 제목u   <p>수정된 본문</p>)post_idr   r   N)r,   r:   r@   rL   rV   r   r   rb   r   r   	edit_postr   s          r   test_edit_post_successr     s      D
$
'C -G3GDM!(+L	@-	P 	
#lBST#,$#?  mm$-  
 	
 	
	
 	

	
	
 	
s0   AB#?BBB	B#BB B#c                   K   t               }t               t        d      _        t               t        d      _        dfd}t               }t        |      |_        t        |      |_        t        |      }t        |      }t        |	      }t        |       }t        d
|      5  t        dt        |            }t        d      |_        |j                  d       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}
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   }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}||	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   }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7 x# 1 sw Y   xxY ww)"uP  get_post_status()가 post_id와 상태 정보를 포함한 dict를 반환해야 한다.

    구현 패턴:
        row = page.locator(row_selector)          ← MagicMock
        title = await row.locator(child).inner_text(timeout=...)   ← chained AsyncMock
        status_text = await row.locator(child).inner_text(timeout=...)
    r   r   u	   비공개r   c                      dz  dk(  rS S )N   r   )selector_child_call_countmock_child_statusmock_child_titles    r   _child_locatorz,test_get_post_status.<locals>._child_locator  s"    Q!##  r   r   r   r   rI   rZ   r[   rh   Tr   )r   Nr   r   statusdictr   r   inz%(py1)s in %(py3)sr   r   r   ro   ri   z%(py1)s == %(py4)sr   rt   assert %(py6)spy6r   )r,   r   r   r   r'   r:   r@   rL   rV   r   r   rb   r   get_post_statusr   r   r|   r}   rz   r~   r   r   r   r{   )rM   r+   r   mock_rowr8   r>   rK   rT   rc   r   r   r   @py_assert0r   r   r   @py_format7r   r   r   s                    @@@r   test_get_post_statusr     s     D !{"+9K"L!#,+#F  ! {H ^<H(3DL
$
'C -G3GDM!(+L	@-	P 9#lBST#,$#? **4*88	9 fd########:###:######f###f######d###d##########999)$$$$$$$$$$$$$$$$$$$8v8v8vv((y(y((((y((((((y((((((( 9	9 9s1   B1Q;6=Q.3Q+4Q.8M3Q;+Q..Q83Q;c                   K   ddl }d}| dz  }|j                  d       t        |       }t               }t	        d|i      |_        |j                         }|j                         }|j                  |       t               }||_	        t               }	t	        |      |	_
        t	        d      |	_        t               }
t        |	      |
_        t               }t	               |_        t        |      |
_        t!        |
	      }t#        |
      }t%        |      }t'        d|      5  t)        dt+        |            }t	        d      |_        |j/                  t+        |             d{   }ddd       t1        t*              }|sddt3        j4                         v st7        j8                  t0              rt7        j:                  t0              nddt3        j4                         v st7        j8                  |      rt7        j:                  |      nddt3        j4                         v st7        j8                  t*              rt7        j:                  t*              ndt7        j:                  |      dz  }t=        t7        j>                  |            d}|j@                  }d} ||      }|sddt3        j4                         v st7        j8                  |      rt7        j:                  |      ndt7        j:                  |      t7        j:                  |      t7        j:                  |      dz  }t=        t7        j>                  |            dx}x}}y7 # 1 sw Y   xY ww)u%  이미지 파일 경로를 전달하면 upload_image()가 업로드된 URL을 반환해야 한다.

    구현 패턴:
        async with page.expect_response(...) as response_info:
            await file_input.set_input_files(image_path)
        response = await response_info.value          ← value가 직접 awaitable
        response_json = await response.json()

    `await response_info.value` 는 `.value` 속성이 코루틴/Future여야 한다.
    asyncio.Future를 사용하여 mock_response를 즉시 반환하도록 설정한다.
    r   Nz-https://img1.daumcdn.net/thumb/test_image.pngztest_image.pngsl   PNG

                                                                                                    r   r   Fr   r   rI   rZ   r[   rh   T)
image_pathr   r   rb   r   zhttps://zLassert %(py6)s
{%(py6)s = %(py2)s
{%(py2)s = %(py0)s.startswith
}(%(py4)s)
})rm   rn   rt   r   )!asynciowrite_bytesrV   r   r   rR   get_event_loopcreate_future
set_resultr1   
__aenter__	__aexit__r,   r(   set_input_filesr'   r:   r@   rL   r   r   rb   r   upload_imager   r|   r}   rz   r~   r   r   r   
startswith)rM   r   expected_url
image_filerT   mock_responseloopresponse_futuremock_response_infomock_cmr+   mock_file_inputr8   r>   rK   rc   r   r   r   r   @py_assert5r   s                         r   test_upload_image_successr     s\     BL ,,J?@!(+L KM"0EFM !!#D&*&8&8&:O}-". kG"0BCG!u5GD$':D  kO&/kO#/:DL
$
'C -G3GDM	@-	P A#lBST#,$#? $$J$@@	A c3::cc33>>%*%>*%%%%%%%%3%%%3%%%>%%%*%%%%%%%%%% A	A As2   D(M-*AM 0M1M 5G(M-M  M*%M-c                   K   t               }t        |      }t        |      }t        |      }t	        |       }t        d|      5  t        dt        |            }||_        ||_	        ||_
        |j                          d{    ddd       |j                  j                          |j                  j                          |j                  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$                  |      dz  }
dd|
iz  }t'        t        j(                  |            dx}x}	}|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}||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7 # 1 sw Y   xY ww)uQ   close()를 호출하면 page/context/browser 리소스가 정리되어야 한다.r   r   rI   rZ   r[   rh   Nr   )z-%(py2)s
{%(py2)s = %(py0)s._page
} is %(py5)src   rl   rp   rq   )z0%(py2)s
{%(py2)s = %(py0)s._context
} is %(py5)s)z0%(py2)s
{%(py2)s = %(py0)s._browser
} is %(py5)s)r,   r:   r@   rL   rV   r   r   rb   r9   _contextrF   r&   assert_awaited_oncerz   r{   r|   r}   r~   r   r   r   )rM   r+   r8   r>   rK   rT   rc   r   r   r   r   r   s               r   
test_closer  ;  s     D
$
'C -G3GDM!(+L	@-	P #lBST 	iik 	JJ""$II!!#MM%%' 9999339<<4<4<433<4<<4<4<433<4 	 s1   AM?L;L8L;J,M8L;;M Mc                  h   t        j                  t        j                        } | j                  }d}||v }|st        j                  d|fd||f      t        j                  |      dt        j                         v st        j                  |      rt        j                  |      nddz  }t        j                  d      dz   d|iz  }t        t        j                  |            d	x}}|d   }|j                  }d
}||k(  }|st        j                  d|fd||f      t        j                  |      t        j                  |      t        j                  |      dz  }t        j                  d|d   j                        dz   d|iz  }	t        t        j                  |	            d	x}x}x}}y	)uD   publish_post()의 visibility 기본값은 'private'이어야 한다.r   r   r   paramsr   u1   publish_post에 visibility 파라미터가 없음z
>assert %(py5)sro   Nr   ri   )z/%(py3)s
{%(py3)s = %(py1)s.default
} == %(py6)s)r   r   r   u-   visibility 기본값이 'private'이 아님: z
>assert %(py8)spy8)inspect	signaturer   r   
parametersrz   r{   r   r|   r}   r~   _format_assertmsgr   r   default)
sigr  r   r   r   r   r   r   r   @py_format9s
             r   test_visibility_default_privater  `  sb    

,99
:C^^FV<6!VVV<6VVV<VVVVVV6VVV6VVVV#VVVVVVV|X$$X(1X$	1XFWFWX$	X XNWi 	X XNWi 	%X XNWi )2X XFWFW	6vl7K7S7S6VWX X XDWDWX X Xr   _PIPELINE_IMPORT_ERROR)parse_html_filerun_pipelineu   publish_pipeline 미구현: c                    | dz  }|j                  dd       t        |      }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)uN   HTML 파일에서 <title> 또는 <h1> 태그의 제목을 추출해야 한다.	post.htmlu   <!DOCTYPE html>
<html>
<head><title>파이썬으로 배우는 Playwright</title></head>
<body><h1>파이썬으로 배우는 Playwright</h1></body>
</html>utf-8encoding
Playwrightr   u	   파이썬r   )z%(py3)s in %(py6)sr   r   %(py8)sr  )z%(py11)s in %(py14)s)py11py14z%(py16)spy16r   zassert %(py19)spy19N)	rQ   _parse_html_filerz   r{   r   append_format_boolopr   r   )rM   	html_filer   r   r   r   r   r   @py_assert10@py_assert13@py_assert12r   r  @py_format15@py_format17@py_format18@py_format20s                    r   test_pipeline_parse_html_titler(    s     ;&I	
    i(FL<L6'?L<?*LkLVG_Lk_.LLLL<?LLL<LLL?LLLLLLLk_LLLkLLL_LLLLLLLLLLLLLLr   c                    | dz  }|j                  dd       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
}g }d}|j                  } |       }	||	v }
|
}|
sd}|j                  } |       }||v }|}|st        j                  d|
fd||	f      t        j                  |      dt	        j
                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |	      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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}x}x}}y
)u>   HTML meta keywords에서 태그 목록을 추출해야 한다.r  u   <!DOCTYPE html>
<html>
<head>
  <title>테스트</title>
  <meta name="keywords" content="python, playwright, automation">
</head>
<body><p>본문</p></body>
</html>r  r  keywordsr   r   rb   r   Nr   r   r   )zD%(py3)s in %(py9)s
{%(py9)s = %(py7)s
{%(py7)s = %(py5)s.lower
}()
})r   ro   rq   rv   z%(py11)sr  )zJ%(py14)s in %(py20)s
{%(py20)s = %(py18)s
{%(py18)s = %(py16)s.lower
}()
})r  r  py18py20z%(py22)spy22r   zassert %(py25)spy25)rQ   r  r   rb   r|   r}   rz   r~   r   r   r   lowerr{   r  r  )rM   r   r   r*  r   r   r   r   r   @py_assert8r   r   r"  @py_assert17@py_assert19@py_assert15r   @py_format12@py_format21@py_format23@py_format24@py_format26s                         r   test_pipeline_parse_html_tagsr9    s    ;&I	   
 i(Fj!Hh$$$$$$$$:$$$:$$$$$$h$$$h$$$$$$$$$$$$$$$$$$$K8Kx~~K~'K8''K<K8>>K>;KK<;K+KKKKK8'KKK8KKKKKKxKKKxKKK~KKK'KKKKKKK<;KKKK<KKKKKK8KKK8KKK>KKK;KKKKKKKKKKKKKKKKr   c                      t        d      } t        j                  t              5  t	        |        ddd       y# 1 sw Y   yxY w)uY   존재하지 않는 HTML 파일을 파싱하면 FileNotFoundError가 발생해야 한다.z(/tmp/nonexistent_tistory_post_12345.htmlN)r   r`   r   r   r  )missings    r   test_pipeline_file_not_foundr<    s9     =>G	(	) "!" " "s	   :Ac                   K   | dz  }|j                  dd       t        |      }|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   }||v }|slt        j                  d|fd||f      t        j                  |      t        j                  |      d	z  }d
d|iz  }t        t        j                  |            dx}x}}g }|d   }d}	||	u}
|
}|
r|d   }t        |      }d}||kD  }|}|s[t        j                  d|
fd||	f      t        j                  |      t        j                  |	      dz  }dd|iz  }|j                  |       |
rt        j                  dfdf      dt        j                         v st        j                  t              rt        j                  t              ndt        j                        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}x}}yw) u_   전체 파이프라인 흐름: parse_html_file → run_pipeline → publish_post 호출 확인.r  u   <!DOCTYPE html>
<html>
<head>
  <title>파이프라인 통합 테스트</title>
  <meta name="keywords" content="test, pipeline">
</head>
<body><p>파이프라인 본문입니다.</p></body>
</html>r  r  r   u    파이프라인 통합 테스트ri   r   r   r   r   Ntestr*  r   )z%(py1)s in %(py4)sbodyr   r   )z%(py3)s is not %(py6)sr  r  r  )>)z4%(py14)s
{%(py14)s = %(py10)s(%(py12)s)
} > %(py17)slen)py10py12r  py17z%(py19)sr  zassert %(py22)sr-  )rQ   r  rz   r{   r   r   r   rA  r  r|   r}   r~   r  )rM   r   r   r   r   r   r   r   r   r   r   @py_assert11r"  @py_assert16r3  r  r&  r'  r5  r6  s                       r   test_pipeline_publish_flowrG    s    
 ;&I	   
 i(F'?@@@?@@@@@?@@@@?@@@@@@@@@@@'VJ''6'''''6''''6'''''''''''A6&>AA>%AfVnA#n*=AA*=*AAAAA>AAA>AAAAAAAAAA*=AAAAAA#AAA#AAAnAAA*=AAAAAAAAAAAAAAAAAAs   J>K )z+https://testblog.tistory.com/manage/post/42r   )L__doc__builtinsr|   _pytest.assertion.rewrite	assertionrewriterz   r  rR   rx   syspathlibr   unittest.mockr   r   r   r`   pytest_asynciory   get
_WORKSPACErb   pathinsertr   ImportError__annotations__scripts.blog.tistory_publisherr	   r
   r   r   _er*   r   markskipif_SKIP_IF_NO_IMPLr,   r:   r@   rG   rL   rV   fixturerX   rd   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r  r  r  r  _run_pipelinescripts.blog.publish_pipeliner  r  _pe_SKIP_IF_NO_PIPELINEr(  r9  r<  rG  r   r   r   <module>ra     s  #J    	 
  5 5   "**..!13HIJ
z?#(("HHOOAs:' %){T! (F  ;;%%)-9 &  & QZ *	Y- 	 		D 0 I y4/ 9 Y-=  (T d  - -
  * 1 1 ! !& "  "      "   , %  %2 %  %4 
O  
O V  V0 
  
4 3)  3)v ;&  ;&F     F X X .2 d* 1 !K& M {{))$&)*@)AB *   M M  L L* " " B   B[  FM4ylBGb9L2YL"EFd  ! !s0   M "N N%M??NNNN