
    (<i>                        d 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j                  j                  d e ee      j                               ddlmZmZmZmZmZmZmZmZ  G d d      Z G d d	      Z G d
 d      Z G d d      Z G d d      Z G d d      Z G d d      Z G d d      Z G d d      Z G d d      Z  G d d      Z! G d d      Z" G d d      Z# G d  d!      Z$ G d" d#      Z% G d$ d%      Z& G d& d'      Z' G d( d)      Z( G d* d+      Z) G d, d-      Z* G d. d/      Z+ G d0 d1      Z, G d2 d3      Z- G d4 d5      Z.y)6u  TDD 테스트 — image_router.py 이미지 생성 라우터 + fallback.

테스트 범위:
- ImageType enum 존재 및 값
- GenerationResult dataclass 필드
- route_image_type() 라우팅 로직 (한글/영문 모두)
- generate_image() 정상 경로
- generate_image() fallback 시나리오
- _extract_structured_json() JSON 추출 로직
- _render_json_to_png() Satori JSON 렌더링
- fallback 최대 2회 제한
- 로그 기록 (log.warning 포맷)
- 모든 외부 API는 unittest.mock으로 차단
    N)Path)	MagicMockpatch)GenerationResult	ImageType_extract_structured_json_generate_infographic_render_html_to_png_render_json_to_pnggenerate_imageroute_image_typec                   D    e 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)
TestImageTypeEnumNc                 (    t        t        d      sJ y NPHOTOREALISTIChasattrr   selfs    U/home/jay/workspace/.worktrees/task-2057-dev2/tools/ai-image-gen/test_image_router.pytest_has_photorealisticz)TestImageTypeEnum.test_has_photorealistic*   s    y"2333    c                 (    t        t        d      sJ y )NCARDNEWSr   r   s    r   test_has_cardnewsz#TestImageTypeEnum.test_has_cardnews-   s    y*---r   c                 (    t        t        d      sJ y NHYBRIDr   r   s    r   test_has_hybridz!TestImageTypeEnum.test_has_hybrid0   s    y(+++r   c                 >    t        t        t                    dk(  sJ y )N   )lenlistr   r   s    r   test_three_membersz$TestImageTypeEnum.test_three_members3   s    4	?#q(((r   c                 B    t         j                  j                  dk(  sJ y Nphotorealistic)r   r   valuer   s    r   test_photorealistic_valuez+TestImageTypeEnum.test_photorealistic_value6   s    ''--1AAAAr   c                 B    t         j                  j                  dk(  sJ y Ncardnews)r   r   r)   r   s    r   test_cardnews_valuez%TestImageTypeEnum.test_cardnews_value9   s    !!'':555r   c                 B    t         j                  j                  dk(  sJ y Nhybrid)r   r   r)   r   s    r   test_hybrid_valuez#TestImageTypeEnum.test_hybrid_value<   s    %%111r   returnN)
__name__
__module____qualname__r   r   r    r%   r*   r.   r2    r   r   r   r   )   s&    4.,)B62r   r   c                   D    e 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)
TestGenerationResultDataclassNc           	      Z    t        dt        d      dddd d      }|j                  du sJ y )NT/tmp/test.pnggeminiF         ?success
image_pathmethod_usedfallback_usedattemptserror_messageelapsed_seconds)r   r   rA   r   rs     r   test_success_fieldz0TestGenerationResultDataclass.test_success_fieldF   s:    O, 
 yyD   r   c           	      `    t        d      }t        d|dddd d      }|j                  |k(  sJ y )Nr<   Tr=   Fr>   r?   r@   )r   r   rB   )r   prI   s      r   test_image_path_fieldz3TestGenerationResultDataclass.test_image_path_fieldR   s?    ! 
 ||q   r   c           	      J    t        dd ddddd      }|j                  dk(  sJ y )NFsatoriT      타임아웃g      ?r@   )r   rC   rH   s     r   test_method_used_fieldz4TestGenerationResultDataclass.test_method_used_field_   s5     (
 }}(((r   c           	      H    t        dd ddddd      }|j                  du sJ y )NFrO   TrP   u   에러g?r@   )r   rD   rH   s     r   test_fallback_used_fieldz6TestGenerationResultDataclass.test_fallback_used_fieldk   s5     "
 $&&&r   c           	      \    t        dt        d      dddd d      }|j                  dk(  sJ y NT
/tmp/x.pngr=   Fr>   g       @r@   )r   r   rE   rH   s     r   test_attempts_fieldz1TestGenerationResultDataclass.test_attempts_fieldw   s8    L) 
 zzQr   c           	      V    t        dt        d      dddd d      }|j                  J y rV   )r   r   rF   rH   s     r   $test_error_message_none_when_successzBTestGenerationResultDataclass.test_error_message_none_when_success   s8    L) 
 &&&r   c           	          t        dt        d      dddd d      }|j                  t        j                  d      k(  sJ y )NTrW   rO   Fr>   g333333?r@   )r   r   rG   pytestapproxrH   s     r   test_elapsed_seconds_fieldz8TestGenerationResultDataclass.test_elapsed_seconds_field   sE    L) 
   FMM#$6666r   r3   )
r5   r6   r7   rJ   rM   rR   rT   rX   rZ   r^   r8   r   r   r:   r:   E   s%    
!!
)
'

'
7r   r:   c                   <    e Zd ZddZddZddZddZddZddZy)	TestRouteImageTypeEnglishNc                 @    t        d      t        j                  k(  sJ y r'   r   r   r   r   s    r   test_photorealistic_keywordz5TestRouteImageTypeEnglish.test_photorealistic_keyword        01Y5M5MMMMr   c                 @    t        d      t        j                  k(  sJ y r,   r   r   r   r   s    r   test_cardnews_keywordz/TestRouteImageTypeEnglish.test_cardnews_keyword       
+y/A/AAAAr   c                 @    t        d      t        j                  k(  sJ y r0   r   r   r   r   s    r   test_hybrid_keywordz-TestRouteImageTypeEnglish.test_hybrid_keyword       )Y-=-====r   c                 @    t        d      t        j                  k(  sJ y r   rb   r   s    r   $test_case_insensitive_photorealisticz>TestRouteImageTypeEnglish.test_case_insensitive_photorealistic   rd   r   c                 @    t        d      t        j                  k(  sJ y )NCardNewsrf   r   s    r   test_case_insensitive_cardnewsz8TestRouteImageTypeEnglish.test_case_insensitive_cardnews   rh   r   c                 @    t        d      t        j                  k(  sJ y r   rj   r   s    r   test_case_insensitive_hybridz6TestRouteImageTypeEnglish.test_case_insensitive_hybrid   rl   r   r3   )	r5   r6   r7   rc   rg   rk   rn   rq   rs   r8   r   r   r`   r`      s$    NB>NB>r   r`   c                   D    e 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)
TestRouteImageTypeKoreanNc                 @    t        d      t        j                  k(  sJ y )Nu   광고rb   r   s    r   #test_gwanggo_maps_to_photorealisticz<TestRouteImageTypeKorean.test_gwanggo_maps_to_photorealistic       )Y-E-EEEEr   c                 @    t        d      t        j                  k(  sJ y )Nu   포토rb   r   s    r   !test_photo_maps_to_photorealisticz:TestRouteImageTypeKorean.test_photo_maps_to_photorealistic   rx   r   c                 @    t        d      t        j                  k(  sJ y )N   카드뉴스rf   r   s    r   %test_cardnews_korean_maps_to_cardnewsz>TestRouteImageTypeKorean.test_cardnews_korean_maps_to_cardnews   s    /93E3EEEEr   c                 @    t        d      t        j                  k(  sJ y )N   배너rf   r   s    r   test_banner_maps_to_cardnewsz5TestRouteImageTypeKorean.test_banner_maps_to_cardnews   s    )Y-?-????r   c                 @    t        d      t        j                  k(  sJ y )Nu   인포그래픽rf   r   s    r   !test_infographic_maps_to_cardnewsz:TestRouteImageTypeKorean.test_infographic_maps_to_cardnews   s     12i6H6HHHHr   c                 @    t        d      t        j                  k(  sJ y )N   한글+사진rj   r   s    r    test_korean_photo_maps_to_hybridz9TestRouteImageTypeKorean.test_korean_photo_maps_to_hybrid   s    0I4D4DDDDr   c                 @    t        d      t        j                  k(  sJ y )Nu   텍스트오버레이rj   r   s    r    test_text_overlay_maps_to_hybridz9TestRouteImageTypeKorean.test_text_overlay_maps_to_hybrid   s     78I<L<LLLLr   r3   )
r5   r6   r7   rw   rz   r}   r   r   r   r   r8   r   r   ru   ru      s,    FFF@IEMr   ru   c                       e Zd ZddZddZy)TestRouteImageTypeUnknownNc                 z    t        j                  t        d      5  t        d       d d d        y # 1 sw Y   y xY w)Nu   알 수 없는 용도)matchu   알수없는용도xyzr\   raises
ValueErrorr   r   s    r   'test_unknown_purpose_raises_value_errorzATestRouteImageTypeUnknown.test_unknown_purpose_raises_value_error   s/    ]]:-DE 	645	6 	6 	6s   1:c                 v    t        j                  t              5  t        d       d d d        y # 1 sw Y   y xY w)N r   r   s    r   $test_empty_string_raises_value_errorz>TestRouteImageTypeUnknown.test_empty_string_raises_value_error   s+    ]]:& 	!R 	! 	! 	!s   /8r3   )r5   r6   r7   r   r   r8   r   r   r   r      s    6!r   r   c                   |    e Zd ZdeddfdZdeddfdZdeddfdZdeddfdZdeddfdZdeddfd	Z	deddfd
Z
y)&TestGenerateImagePhotorealisticSuccesstmp_pathr4   Nc                     t        dd      5  t        ddd|      }d d d        t        t              sJ y # 1 sw Y   xY w)Nimage_router._generate_geminiTreturn_valuer(   u   테스트 프롬프트u   테스트브랜드purposepromptbrand
output_dir)r   r   
isinstancer   r   r   results      r   test_returns_generation_resultzETestGenerateImagePhotorealisticSuccess.test_returns_generation_result   sK    2F 	#(/*#	F	 &"2333	 	s	   9Ac                     t        dd      5  t        ddd|      }d d d        j                  du sJ y # 1 sw Y   xY wNr   Tr   r(   	   테스트	   브랜드r   r   r   rA   r   s      r   test_success_is_truez;TestGenerateImagePhotorealisticSuccess.test_success_is_true   sL    2F 	#("!#	F	 ~~%%%	 		   7A c                     t        dd      5  t        ddd|      }d d d        j                  dk(  sJ y # 1 sw Y   xY w)	Nr   Tr   r(   r   r   r   r=   r   r   rC   r   s      r   test_method_used_is_geminizATestGenerateImagePhotorealisticSuccess.test_method_used_is_gemini   sN    2F 	#("!#	F	 !!X---	 		   8Ac                     t        dd      5  t        ddd|      }d d d        j                  du sJ y # 1 sw Y   xY w)	Nr   Tr   r(   r   r   r   Fr   r   rD   r   s      r   test_fallback_not_usedz=TestGenerateImagePhotorealisticSuccess.test_fallback_not_used  sN    2F 	#("!#	F	 ##u,,,	 	r   c                     t        dd      5  t        ddd|      }d d d        j                  dk(  sJ y # 1 sw Y   xY w)	Nr   Tr   r(   r   r   r   r>   r   r   rE   r   s      r   test_attempts_is_onez;TestGenerateImagePhotorealisticSuccess.test_attempts_is_one  sL    2F 	#("!#	F	 !###	 	r   c                     t        dd      5  t        ddd|      }d d d        j                  J y # 1 sw Y   xY wr   r   r   rF   r   s      r   test_error_message_is_nonezATestGenerateImagePhotorealisticSuccess.test_error_message_is_none  sL    2F 	#("!#	F	 ##+++	 	   5>c                     t        dd      5  t        ddd|      }d d d        j                  dk\  sJ y # 1 sw Y   xY w)	Nr   Tr   r(   r   r   r   g        )r   r   rG   r   s      r   test_elapsed_seconds_positivezDTestGenerateImagePhotorealisticSuccess.test_elapsed_seconds_positive"  sN    2F 	#("!#	F	 %%,,,	 	r   )r5   r6   r7   r   r   r   r   r   r   r   r   r8   r   r   r   r      s    4t 4 4&T &d &.4 .D .-t - -$T $d $,4 ,D ,-d -t -r   r   c                   ,    e Zd ZdeddfdZdeddfdZy) TestGenerateImageCardnewsSuccessr   r4   Nc                     t        dd      5  t        ddd|      }d d d        j                  dk(  sJ y # 1 sw Y   xY w)	Nimage_router._generate_satoriTr   r|   u   카드뉴스 프롬프트r   r   rO   r   r   s      r   test_method_used_is_satoriz;TestGenerateImageCardnewsSuccess.test_method_used_is_satori3  sN    2F 	#&2!#	F	 !!X---	 	r   c                     t        dd      5  t        ddd|      }d d d        j                  du sJ y # 1 sw Y   xY w)Nr   Tr   r   u   배너 프롬프트r   r   r   r   s      r   test_success_true_satoriz9TestGenerateImageCardnewsSuccess.test_success_true_satori=  sL    2F 	# ,!#	F	 ~~%%%	 	r   )r5   r6   r7   r   r   r   r8   r   r   r   r   2  (    .4 .D .& &$ &r   r   c                   ,    e Zd ZdeddfdZdeddfdZy)TestGenerateImageHybridSuccessr   r4   Nc                     t        dd      5  t        ddd|      }d d d        j                  dk(  sJ y # 1 sw Y   xY w)Nimage_router._generate_hybridTr   r1   u   하이브리드 프롬프트r   r   r   r   s      r   test_method_used_is_hybridz9TestGenerateImageHybridSuccess.test_method_used_is_hybridN  sN    2F 	# 5!#	F	 !!X---	 	r   c                     t        dd      5  t        ddd|      }d d d        j                  du sJ y # 1 sw Y   xY w)Nr   Tr   r   u   하이브리드r   r   r   r   s      r   test_success_true_hybridz7TestGenerateImageHybridSuccess.test_success_true_hybridX  sL    2F 	#'(!#	F	 ~~%%%	 	r   )r5   r6   r7   r   r   r   r8   r   r   r   r   M  r   r   r   c                   P    e Zd ZdZdeddfdZdeddfdZdeddfdZdeddfdZy)	TestGenerateImageGeminiFailureu@   Gemini 실패 시 fallback 없이 에러 반환 (GPT 제거됨).r   r4   Nc                     t        dd      5  t        ddd|      }d d d        j                  du sJ y # 1 sw Y   xY wNr   Fr   r(   r   r   r   r   r   s      r   !test_gemini_failure_returns_errorz@TestGenerateImageGeminiFailure.test_gemini_failure_returns_errork  sL    2G 	#("!#	F	 ~~&&&	 	r   c                     t        dd      5  t        ddd|      }d d d        j                  J y # 1 sw Y   xY wr   r   r   s      r   %test_gemini_failure_error_message_setzDTestGenerateImageGeminiFailure.test_gemini_failure_error_message_setu  sL    2G 	#("!#	F	 ##///	 	r   c                     t        dd      5  t        ddd|      }d d d        j                  du sJ y # 1 sw Y   xY wr   r   r   s      r   test_gemini_failure_no_fallbackz>TestGenerateImageGeminiFailure.test_gemini_failure_no_fallback  sN    2G 	#("!#	F	 ##u,,,	 	r   c                     t        dd      5  t        ddd|      }d d d        j                  dk(  sJ y # 1 sw Y   xY w)	Nr   Fr   r(   r   r   r   r>   r   r   s      r    test_gemini_failure_attempts_onez?TestGenerateImageGeminiFailure.test_gemini_failure_attempts_one  sL    2G 	#("!#	F	 !###	 	r   )	r5   r6   r7   __doc__r   r   r   r   r   r8   r   r   r   r   h  sQ    J'$ '4 '0d 0t 0- - -$ $$ $r   r   c                   ,    e Zd ZdeddfdZdeddfdZy)TestGenerateImageSatoriFailurer   r4   Nc                     t        dd      5  t        ddd|      }d d d        j                  du sJ y # 1 sw Y   xY w)Nr   Fr   r|   r   r   r   r   r   s      r   !test_satori_failure_returns_errorz@TestGenerateImageSatoriFailure.test_satori_failure_returns_error  sL    2G 	#&"!#	F	 ~~&&&	 	r   c                     t        dd      5  t        ddd|      }d d d        j                  J t        |j                        dkD  sJ y # 1 sw Y   2xY w)	Nr   Fr   r|   r   r   r   r   )r   r   rF   r#   r   s      r   %test_satori_failure_has_error_messagezDTestGenerateImageSatoriFailure.test_satori_failure_has_error_message  se    2G 	#&"!#	F	 ##///6''(1,,,	 	s   AA)r5   r6   r7   r   r   r   r8   r   r   r   r     s(    '$ '4 '	-d 	-t 	-r   r   c                   d    e Zd ZdeddfdZdeddfdZdeddfdZdedej                  ddfdZ	y)	TestGenerateImageHybridFallbackr   r4   Nc                     t        dd      5  t        dd      5  t        ddd|	      }d d d        d d d        j                  du sJ y # 1 sw Y   "xY w# 1 sw Y   &xY w
Nr   Fr   r   Tr1   r   r   r   r   r   s      r   (test_hybrid_failure_falls_back_to_geminizHTestGenerateImageHybridFallback.test_hybrid_failure_falls_back_to_gemini  sn    1F		1E		 $ "!#	F			 		 ~~%%%		 		 		 		!   AAAA	AA"c                     t        dd      5  t        dd      5  t        ddd|	      }d d d        d d d        j                  d
k(  sJ y # 1 sw Y   #xY w# 1 sw Y   'xY w)Nr   Fr   r   Tr1   r   r   r   r=   r   r   s      r   *test_hybrid_fallback_method_used_is_geminizJTestGenerateImageHybridFallback.test_hybrid_fallback_method_used_is_gemini  sp    1F		1E		 $ "!#	F			 		 !!X---		 		 		 		!   AAAA	AA#c                     t        dd      5  t        dd      5  t        ddd|	      }d d d        d d d        j                  du sJ y # 1 sw Y   "xY w# 1 sw Y   &xY wr   r   r   s      r   !test_hybrid_fallback_used_is_truezATestGenerateImageHybridFallback.test_hybrid_fallback_used_is_true  sp    1F		1E		 $ "!#	F			 		 ##t+++		 		 		 		r   caplogc           	         t        dd      5  t        dd      5  |j                  t        j                  d      5  t	        dd	d
|       d d d        d d d        d d d        |j
                  D cg c]  }d|j                  v s| }}t        |      dk\  sJ y # 1 sw Y   RxY w# 1 sw Y   VxY w# 1 sw Y   ZxY wc c}w )Nr   Fr   r   Timage_routerloggerr1   r   r   r   
[FALLBACK]r>   )r   at_levelloggingWARNINGr   recordsmessager#   r   r   r   rI   fallback_logss        r   test_hybrid_fallback_loggedz;TestGenerateImageHybridFallback.test_hybrid_fallback_logged  s    1F
	1E
	 OOGOONOC
	
  "!#	
	 
	 
	 %+NNPqlaii6OPP=!Q&&&
	 
	 
	 
	 
	 
	 QsE   B7"B+BB+B74CCB($B++B4	0B77C )
r5   r6   r7   r   r   r   r   r\   LogCaptureFixturer   r8   r   r   r   r     s\    & &$ &.4 .D .,$ ,4 ,'D '&BZBZ '_c 'r   r   c                   <    e Zd ZdeddfdZdeddfdZdeddfdZy)TestFallbackMaxAttemptsr   r4   Nc                     t        dd      5  t        ddd|      }ddd       j                  du sJ |j                  d	k(  sJ y# 1 sw Y   +xY w)
u9   Gemini 실패 → 에러 반환 (GPT fallback 제거됨).r   Fr   r(   r   r   r   Nr>   r   r   rA   rE   r   s      r   -test_gemini_failure_returns_error_no_fallbackzETestFallbackMaxAttempts.test_gemini_failure_returns_error_no_fallback  s^    2G 	#("!#	F	 ~~&&&!###	 	s   AAc                     t        dd      5  t        dd      5  t        ddd|      }d	d	d	       d	d	d	       j                  du sJ |j                  d
k(  sJ y	# 1 sw Y   3xY w# 1 sw Y   7xY w)u9   Hybrid + Gemini fallback 모두 실패 시 에러 반환.r   Fr   r   r1   r   r   r   NrP   r   r   s      r   #test_hybrid_both_fail_returns_errorz;TestFallbackMaxAttempts.test_hybrid_both_fail_returns_error  s     1F		1F		 $ "!#	F			 		 ~~&&&!###		 		 		 		!   A*AA*A'	#A**A3c                     t        dd      5  t        dd      5  t        ddd|      }d	d	d	       d	d	d	       j                  du sJ |j                  d
k(  sJ y	# 1 sw Y   3xY w# 1 sw Y   7xY w)u>   Infographic + Satori fallback 모두 실패 시 에러 반환."image_router._generate_infographicFr   r   infographicr   r   r   NrP   r   r   s      r   (test_infographic_both_fail_returns_errorz@TestFallbackMaxAttempts.test_infographic_both_fail_returns_error  s     6UK		1F		 $%"!#	F			 		 ~~&&&!###		 		 		 		r   )r5   r6   r7   r   r   r   r   r8   r   r   r   r     s;    
$d 
$t 
$$D $T $$ $$ $r   r   c                   T    e Zd ZdeddfdZdedej                  ddfdZdeddfdZy)"TestGenerateImageExceptionHandlingr   r4   Nc                     t        dt        d            5  t        ddd|      }ddd       j                  d	u sJ |j                  J y# 1 sw Y   (xY w)
uD   Gemini가 예외를 발생시키면 에러 반환 (fallback 없음).r   u   API 연결 실패side_effectr(   r   r   r   NFr   RuntimeErrorr   rA   rF   r   s      r   *test_gemini_raises_exception_returns_errorzMTestGenerateImageExceptionHandling.test_gemini_raises_exception_returns_error$  si    +$%89
 		 $("!#	F			 ~~&&&##///		 		   AAr   c                 |   t        dt        d            5  |j                  t        j                  d      5  t        ddd|	       d
d
d
       d
d
d
       |j                  D cg c]"  }|j                  t        j                  k(  s!|$ }}t        |      dk\  sJ y
# 1 sw Y   YxY w# 1 sw Y   ]xY wc c}w )u0   예외 발생 시 경고 로그가 기록된다.r   rQ   r  r   r   r(   r   r   r   Nr>   )	r   TimeoutErrorr   r   r   r   r   levelnor#   r   s        r   test_gemini_exception_loggedz?TestGenerateImageExceptionHandling.test_gemini_exception_logged3  s     /(8	
 OOGOONOC	 ("!#		 	 %+NNSqaii7??6RSS=!Q&&&	 	 	 	 Ts.   "B-B!	B-'"B9
B9!B*	&B--B6c                     t        dt        d            5  t        ddd|      }ddd       j                  d	u sJ |j                  J y# 1 sw Y   (xY w)
u9   Satori 예외 발생 시 에러 반환 (fallback 없음).r   u   HTML 파싱 오류r  r|   r   r   r   NFr  r   s      r    test_satori_raises_returns_errorzCTestGenerateImageExceptionHandling.test_satori_raises_returns_errorE  si    +$%9:
 		 $&"!#	F			 ~~&&&##///		 		r	  )	r5   r6   r7   r   r  r\   r   r  r  r8   r   r   r  r  #  sI    04 0D 0'T '6C[C[ '`d '$0 0$ 0r   r  c                   \    e Zd Zdedej
                  ddfdZdedej
                  ddfdZy)TestGenerateImageLoggingr   r   r4   Nc                 j   t        dd      5  |j                  t        j                  d      5  t	        ddd|	       d
d
d
       d
d
d
       |j
                  D cg c]"  }|j                  t        j                  k(  s!|$ }}t        |      dk\  sJ y
# 1 sw Y   YxY w# 1 sw Y   ]xY wc c}w )u5   생성 시작 시 INFO 레벨 로그가 기록된다.r   Tr   r   r   r(   r   r   r   Nr>   )r   r   r   INFOr   r   r  r#   )r   r   r   rI   	info_logss        r   test_info_log_on_startz/TestGenerateImageLogging.test_info_log_on_start[  s     1E		OOGLLO@		 ("!#				 		 !'L1!))w||2KQL	L9~"""		 		 		 		 Ms.   "B$B B$"B0B0B!	B$$B-c           	         t        dd      5  t        dd      5  |j                  t        j                  d      5  t	        dd	d
|       ddd       ddd       ddd       |j
                  D cg c]1  }d|j                  v s|j                  t        j                  k(  s0|3 }}t        |      dk\  sJ y# 1 sw Y   pxY w# 1 sw Y   txY w# 1 sw Y   xxY wc c}w )u2   fallback 로그는 WARNING 레벨이어야 한다.r   Fr   r   Tr   r   r1   r   r   r   Nr   r>   )	r   r   r   r   r   r   r   r  r#   r   s        r   #test_warning_log_level_for_fallbackz<TestGenerateImageLogging.test_warning_log_level_for_fallbackj  s     1F
	1E
	 OOGOONOC
	
  "!#	
	 
	 
	 %+NNqqlaii6OTUT]T]ahapapTpqq=!Q&&&
	 
	 
	 
	 
	 
	 rsK   C"C	B=C	C4C!C!&C!=CC		C	CC)r5   r6   r7   r   r\   r   r  r  r8   r   r   r  r  Z  sD    #t #V=U=U #Z^ #'D '&JbJb 'gk 'r   r  c                       e Zd ZddZddZy)TestImageTypeInfographicNc                 (    t        t        d      sJ y)u0   ImageType에 INFOGRAPHIC 멤버가 존재한다.INFOGRAPHICNr   r   s    r   test_has_infographicz-TestImageTypeInfographic.test_has_infographic  s    y-000r   c                 B    t         j                  j                  dk(  sJ y)u4   INFOGRAPHIC의 값은 'infographic' 문자열이다.r   N)r   r  r)   r   s    r   test_infographic_valuez/TestImageTypeInfographic.test_infographic_value  s    $$**m;;;r   r3   )r5   r6   r7   r  r  r8   r   r   r  r    s    1<r   r  c                   4    e Zd ZddZddZddZddZddZy)TestRouteImageTypeInfographicNc                 @    t        d      t        j                  k(  sJ y)u=   'infographic' 키워드는 INFOGRAPHIC으로 라우팅된다.r   Nr   r   r  r   s    r   test_infographic_keywordz6TestRouteImageTypeInfographic.test_infographic_keyword  s    .)2G2GGGGr   c                 @    t        d      t        j                  k(  sJ y)uB   'comparison_table' 키워드는 INFOGRAPHIC으로 라우팅된다.comparison_tableNr"  r   s    r   test_comparison_table_keywordz;TestRouteImageTypeInfographic.test_comparison_table_keyword  s     23y7L7LLLLr   c                 @    t        d      t        j                  k(  sJ y)u;   'checklist' 키워드는 INFOGRAPHIC으로 라우팅된다.	checklistNr"  r   s    r   test_checklist_keywordz4TestRouteImageTypeInfographic.test_checklist_keyword  s    ,	0E0EEEEr   c                 @    t        d      t        j                  k(  sJ y)u>   'process_flow' 키워드는 INFOGRAPHIC으로 라우팅된다.process_flowNr"  r   s    r   test_process_flow_keywordz7TestRouteImageTypeInfographic.test_process_flow_keyword  s    /93H3HHHHr   c                 @    t        d      t        j                  k(  sJ y)u7   'chart' 키워드는 INFOGRAPHIC으로 라우팅된다.chartNr"  r   s    r   test_chart_keywordz0TestRouteImageTypeInfographic.test_chart_keyword  s    (I,A,AAAAr   r3   )r5   r6   r7   r#  r&  r)  r,  r/  r8   r   r   r   r     s     HMFIBr   r   c                   ,    e Zd ZdeddfdZdeddfdZy)#TestGenerateImageInfographicSuccessr   r4   Nc                     t        dd      5  t        ddd|      }ddd       j                  dk(  sJ y# 1 sw Y   xY w)	uE   인포그래픽 생성 성공 시 method_used는 'infographic'이다.r   Tr   r      인포그래픽 프롬프트r   r   Nr   r   s      r   test_method_used_is_infographiczCTestGenerateImageInfographicSuccess.test_method_used_is_infographic  sN    7dK 	#%5!#	F	 !!]222	 	r   c                     t        dd      5  t        ddd|      }ddd       j                  du sJ y# 1 sw Y   xY w)	uC   comparison_table 목적으로 생성 성공 시 success는 True다.r   Tr   r%  u   비교표 프롬프트r   r   Nr   r   s      r   test_success_true_infographiczATestGenerateImageInfographicSuccess.test_success_true_infographic  sL    7dK 	#*/!#	F	 ~~%%%	 	r   )r5   r6   r7   r   r4  r6  r8   r   r   r1  r1    s(    	3 	3 	3	&d 	&t 	&r   r1  c                   L    e Zd ZdeddfdZdeddfdZdeddfdZdeddfdZy)$TestGenerateImageInfographicFallbackr   r4   Nc                     t        dd      5  t        dd      5  t        ddd|	      }d
d
d
       d
d
d
       j                  du sJ |j                  du sJ y
# 1 sw Y   2xY w# 1 sw Y   6xY w)uD   _generate_infographic 실패 시 satori fallback으로 성공한다.r   Fr   r   Tr   r3  r   r   N)r   r   rA   rD   r   s      r   -test_infographic_failure_falls_back_to_satorizRTestGenerateImageInfographicFallback.test_infographic_failure_falls_back_to_satori  s     6UK		1E		 $%5!#	F			 		 ~~%%%##t+++		 		 		 		s!   A)AA)A&	"A))A2c                     t        dd      5  t        dd      5  t        ddd|	      }d
d
d
       d
d
d
       j                  dk(  sJ y
# 1 sw Y   #xY w# 1 sw Y   'xY w)u?   infographic → satori fallback 시 method_used는 'satori'다.r   Fr   r   Tr   r3  r   r   NrO   r   r   s      r   /test_infographic_fallback_method_used_is_satorizTTestGenerateImageInfographicFallback.test_infographic_fallback_method_used_is_satori  sr     6UK		1E		 $%5!#	F			 		 !!X---		 		 		 		r   c                     t        dd      5  t        dd      5  t        ddd|      }d	d	d	       d	d	d	       j                  du sJ y	# 1 sw Y   "xY w# 1 sw Y   &xY w)
u>   infographic과 satori 모두 실패하면 success는 False다.r   Fr   r   r   r3  r   r   Nr   r   s      r   r   zMTestGenerateImageInfographicFallback.test_infographic_both_fail_returns_error  sp     6UK		1F		 $%5!#	F			 		 ~~&&&		 		 		 		r   c                 v    |dz  }t        dd      5  t        d|      }ddd       du sJ y# 1 sw Y   xY w)uK   _extract_structured_json이 None 반환 시 _generate_infographic은 False.out.pngz%image_router._extract_structured_jsonNr   u   [infographic] 설명F)r   r	   )r   r   output_filer   s       r   %test_infographic_fails_when_json_nonezJTestGenerateImageInfographicFallback.test_infographic_fails_when_json_none  sI    *:N 	P*+A;OF	P	P 	Ps   /8)r5   r6   r7   r   r:  r<  r   rA  r8   r   r   r8  r8    sO    ,d ,t ,. .QU .' '$ 'd t r   r8  c                   <    e Zd ZddZddZddZddZddZddZy)	TestExtractStructuredJsonNc                     d}t               }||_        d|_        t        d|      5  t	        dd      }ddd       t        t              sJ |d   dk(  sJ y# 1 sw Y   &xY w)	uF   _extract_structured_json은 유효한 JSON에서 dict를 반환한다.u:   {"type": "infographic", "title": "테스트", "items": []}r   subprocess.runr      설명r   Ntype)r   stdout
returncoder   r   r   dict)r   json_responsemock_resultr   s       r   test_returns_dict_on_valid_jsonz9TestExtractStructuredJson.test_returns_dict_on_valid_json  sp    Tk*!"#+> 	G-hFF	G&$'''f~...	G 	Gs   AA#c                 .   t               }d|_        d|_        t        d|      5 }t	        dd       ddd       j                          |j                  }|d   r|d   d   n|d   j                  d	g       }t        |      }d
|v sJ y# 1 sw Y   YxY w)u<   subprocess.run 호출 시 claude CLI 경로가 포함된다.0{"type": "checklist", "title": "t", "items": []}r   rE  r   rF  r(  Nr>   argsz/home/jay/.local/bin/claude)	r   rH  rI  r   r   assert_called_once	call_argsgetstr)r   rL  mock_runrR  cmdcmd_strs         r   test_calls_claude_cliz/TestExtractStructuredJson.test_calls_claude_cli  s    kO!"#+> 	<($X{;	<##%&&	!*1il1o9Q<3C3CFB3Oc(,777	< 	<s   BBc                     t               }d|_        d|_        t        d|      5  t        d      5  t	        dd      }ddd       ddd       J y# 1 sw Y   xY w# 1 sw Y   xY w)	u4   stdout이 빈 문자열일 때 None을 반환한다.r   r   rE  r   
time.sleeprF  r   Nr   rH  rI  r   r   r   rL  r   s      r   test_empty_result_returns_nonez8TestExtractStructuredJson.test_empty_result_returns_none  ss    k!"#+> 	Gl@S 	G-hFF	G 	G~~	G 	G 	G 	G!   A AA A	A  A)c                     t               }d|_        d|_        t        d|      5  t        d      5  t	        dd      }ddd       ddd       J y# 1 sw Y   xY w# 1 sw Y   xY w)	uF   stdout이 JSON이 아닌 일반 텍스트일 때 None을 반환한다.u3   이것은 JSON이 아닌 일반 텍스트입니다.r   rE  r   rZ  rF  r   Nr[  r\  s      r   !test_non_json_result_returns_nonez;TestExtractStructuredJson.test_non_json_result_returns_none"  ss    kR!"#+> 	Gl@S 	G-hFF	G 	G~~	G 	G 	G 	Gr^  c                     ddddgdgd}t               }d|_        d|_        t        d	|
      5  t	        dd      }ddd       |k(  sJ y# 1 sw Y   xY w)u3   유효한 JSON 응답이 올바르게 파싱된다.r%  u   비교Ax)labelfeaturesrG  titleitemsu]   {"type": "comparison_table", "title": "비교", "items": [{"label": "A", "features": ["x"]}]}r   rE  r   rF  Nr[  )r   expectedrL  r   s       r    test_valid_json_parsed_correctlyz:TestExtractStructuredJson.test_valid_json_parsed_correctly+  sv    .WZilhmMnLopkk 	 "##+> 	L-h8JKF	L!!!	L 	Ls   AAc                     t               }d|_        d|_        t        d|      5 }t	        dd       ddd       j
                  }|d   d   }|d   }d|v sJ y# 1 sw Y   )xY w)	uB   _extract_structured_json 프롬프트에 img_type이 포함된다.rO  r   rE  r   rF  r(  NrP   r   rH  rI  r   r   rR  r   rL  rU  rR  rV  
prompt_args         r   test_prompt_contains_img_typez7TestExtractStructuredJson.test_prompt_contains_img_type7  st    kO!"#+> 	<($X{;	<&&	l1oV
j(((	< 	<   AA$r3   )	r5   r6   r7   rM  rX  r]  r`  rj  ro  r8   r   r   rC  rC     s     	/8
"
)r   rC  c                   D    e 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)
TestExtractStructuredJsonRetryNc                 <   t               }d|_        d|_        t               }d|_        d|_        t        d||g      5 }t        d      5  t	        dd      }d	d	d	       d	d	d	       dd
g dk(  sJ j
                  dk(  sJ y	# 1 sw Y   .xY w# 1 sw Y   2xY w)u,   빈 응답 후 재시도하여 성공한다.r   r   7   {"type": "infographic", "title": "성공", "items": []}rE  r  rZ  rF  r   Nu   성공rf  rP   r   rH  rI  r   r   
call_count)r   
mock_emptymock_successrU  r   s        r   test_retries_on_empty_responsez=TestExtractStructuredJsonRetry.test_retries_on_empty_responseJ  s    [

 !
 {W"##*l1KL 	GPXZ_`lZm 	G-hFF	G 	G-(RPPPP""a'''	G 	G 	G 	Gs$    BBBB	BBc                 .   t               }d|_        d|_        t               }d|_        d|_        t        d||g      5 }t        d      5  t	        dd      }d	d	d	       d	d	d	       J j
                  d
k(  sJ y	# 1 sw Y   'xY w# 1 sw Y   +xY w)u7   JSON이 아닌 응답 후 재시도하여 성공한다.u   일반 텍스트입니다r   u5   {"type": "checklist", "title": "성공", "items": []}rE  r  rZ  rF  r(  NrP   ru  )r   	mock_textrx  rU  r   s        r   !test_retries_on_non_json_responsez@TestExtractStructuredJsonRetry.test_retries_on_non_json_responseW  s    K	6	 	 {U"##)\1JK 	ExY^_kYl 	E-hDF	E 	E!!!""a'''	E 	E 	E 	Es$    BA?B?B	BBc                 <   t               }d|_        d|_        d|_        t               }d|_        d|_        t	        d||g      5 }t	        d      5  t        d	d
      }ddd       ddd       J j                  dk(  sJ y# 1 sw Y   'xY w# 1 sw Y   +xY w)u2   비정상 종료 후 재시도하여 성공한다.r   errorr>   rt  r   rE  r  rZ  rF  r   NrP   )r   rH  stderrrI  r   r   rv  )r   	mock_failrx  rU  r   s        r   #test_retries_on_nonzero_return_codezBTestExtractStructuredJsonRetry.test_retries_on_nonzero_return_coded  s    K		"	 	 {W"##)\1JK 	GxY^_kYl 	G-hFF	G 	G!!!""a'''	G 	G 	G 	Gs$   BB BB	BBc                     t               }d|_        d|_        t        d|      5 }t        d      5  t	        dd      }ddd       ddd       J j
                  d	k(  sJ y# 1 sw Y   'xY w# 1 sw Y   +xY w)
u2   3회 모두 빈 응답이면 None을 반환한다.r   r   rE  r   rZ  rF  r   N   ru  )r   rw  rU  r   s       r   #test_returns_none_after_max_retrieszBTestExtractStructuredJsonRetry.test_returns_none_after_max_retriesr  s    [

 !
#*= 	G5Q]K^ 	G-hFF	G 	G~~""a'''	G 	G 	G 	Gs!   A1A%A1%A.	*A11A:c                     t               }d|_        d|_        t        d|      5  t        d      5 }t	        dd       ddd       ddd       j                          y# 1 sw Y   "xY w# 1 sw Y   &xY w)	u*   첫 시도 성공 시 sleep 호출 없다.rt  r   rE  r   rZ  rF  r   N)r   rH  rI  r   r   assert_not_called)r   rx  
mock_sleeps      r   test_no_sleep_on_first_successz=TestExtractStructuredJsonRetry.test_no_sleep_on_first_success|  sn     {W"##,? 	>|AT 	>Xb$X}=	> 	>$$&	> 	> 	> 	>s!   A,A A, A)	%A,,A5c                 &   t               }d|_        d|_        t               }d|_        d|_        t        d||g      5  t        d      5 }t	        dd       d	d	d	       d	d	d	       j                  d
       y	# 1 sw Y   #xY w# 1 sw Y   'xY w)u,   재시도 시 time.sleep(1)이 호출된다.r   r   rt  rE  r  rZ  rF  r   Nr>   )r   rH  rI  r   r   assert_called_once_with)r   rw  rx  r  s       r   !test_sleep_called_between_retriesz@TestExtractStructuredJsonRetry.test_sleep_called_between_retries  s    [

 !
 {W"##*l1KL 	>eT`Na 	>eo$X}=	> 	>**1-	> 	> 	> 	>s$    BA;B;B	 BBc                 2   t               }d|_        d|_        t               }d|_        d|_        t        d||||g      5 }t        d      5  t	        dd      }d	d	d	       d	d	d	       J j
                  d
k(  sJ y	# 1 sw Y   'xY w# 1 sw Y   +xY w)uJ   JSON 파싱 에러 시 피드백 포함 4번째 재시도가 실행된다.u+   {"type": "infographic", "title": 잘못된}r   rt  rE  r  rZ  rF  r   Nr"   ru  )r   mock_invalidrx  rU  r   s        r   -test_json_parse_error_triggers_feedback_retryzLTestExtractStructuredJsonRetry.test_json_parse_error_triggers_feedback_retry  s     {K"# {W"#"|\[g0hi	Gmu,	G .hFF		G 	G
 !!!""a'''	G 	G 	G 	Gs$   BBBB
	BBr3   )
r5   r6   r7   ry  r|  r  r  r  r  r  r8   r   r   rr  rr  I  s%    (((('
.(r   rr  c                   L    e Zd ZdeddfdZdeddfdZdeddfdZdeddfdZy)TestRenderHtmlToPngr   r4   Nc                    |dz  dt         ddffd}t               }||j                  _        t               }||j                  _        t               }t               }||j                  j                  _        t        |      |_        t        d      |_	        t        d|      5  t        d	      }ddd       d
u sJ y# 1 sw Y   xY w)u5   Playwright 렌더링 성공 시 True를 반환한다.r?  kwargsr4   Nc                  p    t        | j                  d            }t        |      j                  d       y Npath   fake png datarT  rS  r   write_bytesr  rL   r@  s     r   fake_screenshotzITestRenderHtmlToPng.test_returns_true_on_success.<locals>.fake_screenshot  +    FJJv{34AG 01r   r   F#playwright.sync_api.sync_playwright<div>test</div>T)objectr   
screenshotr  new_pager   chromiumlaunch	__enter____exit__r   r
   )	r   r   r  	mock_pagemock_browsermock_context_managermock_playwrightr   r@  s	           @r   test_returns_true_on_successz0TestRenderHtmlToPng.test_returns_true_on_success  s    *	2f 	2 	2 K	+:	( {-6*({#+7C  ''4)2)P&(1u(E%8G[\ 	I():KHF	I~~	I 	Is   )CCc                     t               }t        t        d            |_        t        d      |_        t	        d|      5  t        d|dz        }ddd       du sJ y# 1 sw Y   xY w)	uE   Playwright 렌더링 실패(예외 발생) 시 False를 반환한다.u   Playwright 초기화 실패r  Fr   r  r  r?  N)r   r  r  r  r   r
   )r   r   r  r   s       r   test_returns_false_on_failurez1TestRenderHtmlToPng.test_returns_false_on_failure  so    ({)2|Li?j)k&(1u(E%8G[\ 	R():Hy<PQF	R	R 	Rs   A""A+c                    |dz  dt         ddffd}t               }||j                  _        t               }||j                  _        t               }t               }||j                  j                  _        t        |      |_        t        d      |_	        t        d|      5  t        d	       ddd       |j                  j                  d
   }|j                  d      du sJ y# 1 sw Y   8xY w)u7   screenshot() 호출 시 full_page=True가 전달된다.r?  r  r4   Nc                  p    t        | j                  d            }t        |      j                  d       y r  r  r  s     r   r  zWTestRenderHtmlToPng.test_screenshot_called_with_full_page_true.<locals>.fake_screenshot  r  r   r   Fr  r  r>   	full_pageT)r  r   r  r  r  r   r  r  r  r  r   r
   rR  rS  )	r   r   r  r  r  r  r  call_kwargsr@  s	           @r   *test_screenshot_called_with_full_page_truez>TestRenderHtmlToPng.test_screenshot_called_with_full_page_true  s    *	2f 	2 	2 K	+:	( {-6*({#+7C  ''4)2)P&(1u(E%8G[\ 	@ 1;?	@  **44Q7{+t333	@ 	@s   )C--C6c                 >  	
 |dz  
g 	dt         ddf
fd}t               }||j                  _        dt        dt        ddf	fd}t               }||j
                  _        t               }t               }||j                  j                  _        t        |	      |_	        t        d
	      |_
        t        d|	      5  t        d|      5  t        d
       ddd       ddd       t        	      dkD  sJ 	d   }d|v sJ y# 1 sw Y   -xY w# 1 sw Y   1xY w)uF   생성된 HTML body CSS에 min-height가 사용된다 (height 대신).r?  r  r4   Nc                  p    t        | j                  d            }t        |      j                  d       y r  r  r  s     r   r  zJTestRenderHtmlToPng.test_body_css_uses_min_height.<locals>.fake_screenshot  r  r   content_c                 (    j                  |        y )N)append)r  r  captured_htmls     r   capture_write_textzMTestRenderHtmlToPng.test_body_css_uses_min_height.<locals>.capture_write_text  s      )r   r   Fr  zpathlib.Path.write_textr  r  r   z
min-height)r  r   r  r  rT  r  r   r  r  r  r  r   r
   r#   )r   r   r  r  r  r  r  r  html_contentr  r@  s            @@r   test_body_css_uses_min_heightz1TestRenderHtmlToPng.test_body_css_uses_min_height  s2   *#%	2f 	2 	2 K	+:	(	* 	*# 	*$ 	* !{-6*({#+7C  ''4)2)P&(1u(E% 7FZ[	@+9KL	@   1;?		@ 	@ =!A%%%$Q'|+++	@ 	@ 	@ 	@s$    DDDD	DD)r5   r6   r7   r   r  r  r  r  r8   r   r   r  r    sN    T d ,d t 44 4D 42,d ,t ,r   r  c                   \    e Zd ZdeddfdZdeddfdZdeddfdZdeddfdZdeddfdZy)	TestRenderJsonToPngr   r4   Nc                     |dz  dt         dt         dt        ffd}t        d|      5  t        dd	d
gd      }ddd       du sJ y# 1 sw Y   xY w)u6   Satori JSON 렌더링 성공 시 True를 반환한다.r?  rP  r  r4   c                  L    j                  d       t               }d|_        |S Nr  r   r  r   rI  rP  r  mockr@  s      r   fake_runzBTestRenderJsonToPng.test_returns_true_on_success.<locals>.fake_run  %    ##$45;DDOKr   rE  r  r(  tarf  NT)r  r   r   r   )r   r   r  r   r@  s       @r   r  z0TestRenderJsonToPng.test_returns_true_on_success	  sq    *	F 	f 	 	 #: 	k(+WZV[)\^ijF	k~~	k 	ks   AAc                     t               }d|_        d|_        t        d|      5  t	        ddg d|dz        }d	d	d	       d
u sJ y	# 1 sw Y   xY w)u6   subprocess 비정상 종료 시 False를 반환한다.r>   r~  rE  r   r   r  rf  r?  NF)r   rI  r  r   r   )r   r   rL  r   s       r   "test_returns_false_on_nonzero_exitz6TestRenderJsonToPng.test_returns_false_on_nonzero_exit  sd    k!"$#+> 	s(-#XZ)[]ehq]qrF	s	s 	ss   A		Ac                     |dz  dt         dt         dt        ffd}t        d|      5 }t        dd	g d
       ddd       j                  d   d   }d|v sJ y# 1 sw Y   "xY w)u;   subprocess.run 호출 시 --json 플래그가 포함된다.r?  rP  r  r4   c                  L    j                  d       t               }d|_        |S r  r  r  s      r   r  zFTestRenderJsonToPng.test_calls_satori_with_json_flag.<locals>.fake_run$  r  r   rE  r  r.  r  rf  Nr   --json)r  r   r   r   rR  )r   r   r  rU  rR  r@  s        @r    test_calls_satori_with_json_flagz4TestRenderJsonToPng.test_calls_satori_with_json_flag   s    *	F 	f 	 	 #: 	[h3 LkZ	[&&q)!,	9$$$	[ 	[s   AA'c                 T  	 |dz  	ddddgd}dt         dt         d	t        f	fd
}t        d|      5 }t        |	       ddd       j                  d   d   }|j                  d      dz   }ddl}|j                  ||         }|d   dk(  sJ |d   ddgk(  sJ y# 1 sw Y   ^xY w)u<   JSON 데이터가 올바르게 직렬화되어 전달된다.r?  r(  r   u   항목1u   항목2rf  rP  r  r4   c                  L    j                  d       t               }d|_        |S r  r  r  s      r   r  zITestRenderJsonToPng.test_json_data_serialized_correctly.<locals>.fake_run4  r  r   rE  r  Nr   r  r>   rg  rh  )r  r   r   r   rR  indexjsonloads)
r   r   	test_datar  rU  rR  json_arg_idxr  parsedr@  s
            @r   #test_json_data_serialized_correctlyz7TestRenderJsonToPng.test_json_data_serialized_correctly/  s    *(;)U^I_`		F 	f 	 	 #: 	8h	;7	8&&q)!,	 x014Il34g+---g9i"8888	8 	8s   BB'c                     t        dt        d            5  t        ddi|dz        }ddd       du sJ y# 1 sw Y   xY w)	u(   예외 발생 시 False를 반환한다.rE  znode not foundr  rG  r   r?  NF)r   r  r   r   s      r   test_returns_false_on_exceptionz3TestRenderJsonToPng.test_returns_false_on_exceptionD  sN    #>N1OP 	X(&-)@(YBVWF	X	X 	Xs	   8A)	r5   r6   r7   r   r  r  r  r  r  r8   r   r   r  r    sa    T d 4 D % %$ %9D 9T 9*  r   r  c                   $    e Zd ZddZddZddZy)TestExtractStructuredJsonSchemaNc                     t               }d|_        d|_        t        d|      5 }t	        dd       ddd       j
                  }|d   d   }|d   }d	|v sJ y# 1 sw Y   )xY w)
uT   _extract_structured_json 프롬프트에 'JSON 스키마' 키워드가 포함된다.2{"type": "infographic", "title": "t", "items": []}r   rE  r   rF  r   NrP   u   JSON 스키마rl  rm  s         r   (test_prompt_contains_json_schema_keywordzHTestExtractStructuredJsonSchema.test_prompt_contains_json_schema_keywordQ  st    kQ!"#+> 	>($X}=	>&&	l1oV
:---	> 	>rp  c                     t               }d|_        d|_        t        d|      5 }t	        dd       ddd       j
                  }|d   d   }|d   }d	|v sJ y# 1 sw Y   )xY w)
uK   _extract_structured_json 프롬프트에 'items' 키워드가 포함된다.r  r   rE  r   rF  r   NrP   rh  rl  rm  s         r   "test_prompt_contains_items_keywordzBTestExtractStructuredJsonSchema.test_prompt_contains_items_keyword]  st    kQ!"#+> 	>($X}=	>&&	l1oV
*$$$	> 	>rp  c                     t               }d|_        d|_        t        d|      5 }t	        dd       ddd       j
                  }|d   d   }d|v sJ y# 1 sw Y   $xY w)	u9   _extract_structured_json은 haiku 모델을 사용한다.r  r   rE  r   rF  r   Nhaikurl  )r   rL  rU  rR  rV  s        r   test_prompt_uses_haiku_modelz<TestExtractStructuredJsonSchema.test_prompt_uses_haiku_modeli  si    kQ!"#+> 	>($X}=	>&&	l1o#~~		> 	>s   AAr3   )r5   r6   r7   r  r  r  r8   r   r   r  r  P  s    
.
%	r   r  c                       e Zd ZdZdeddfdZdeddfdZdeddfdZdeddfdZdeddfd	Z	deddfd
Z
deddfdZy)TestValidateImageQualityu1   _validate_image_quality() QC 게이트 테스트.r   r4   Nc                 J    ddl m}  ||dz        \  }}|du sJ d|d   v sJ y)u&   존재하지 않는 파일 → 실패.r   _validate_image_qualityzno_such.pngFu   존재하지 않습니다N)r   r  )r   r   r  passedwarningss        r   test_nonexistent_file_failsz4TestValidateImageQuality.test_nonexistent_file_failsx  s7    828m3KL*hqk999r   c                 p    ddl m} |dz  }|j                  d        ||      \  }}|du sJ d|d   v sJ y)u5   15KB 미만 파일 → 실패 (빈 이미지 의심).r   r  ztiny.pngsd                                                                                                       Fu   최소 15KBN)r   r  r  )r   r   r  
small_filer  r  s         r   test_tiny_file_failsz-TestValidateImageQuality.test_tiny_file_fails  sJ    8
*
}-2:>+++r   c                 ,   ddl m} 	 ddl}ddlm} j                  j                  j                  ddd|j                  	            }|d
z  }|j                  |        ||      \  }}|du sJ y# t
        $ r t        j                  d       Y w xY w)u-   정상 크기+해상도 이미지 → 통과.r   r  NImage   PIL/numpy 미설치2      i  i  r  dtypez	valid.pngT)r   r  numpyPILr  ImportErrorr\   skip	fromarrayrandomrandintuint8save	r   r   r  npr  imgoutr  r  s	            r   test_valid_image_passesz0TestValidateImageQuality.test_valid_image_passes  s    8	/! oobii//Cbhh/WX$237~~  	/KK-.	/s   
A2 2BBc                 p   ddl m} 	 ddl}ddlm} j                  j                  j                  ddd|j                  	            }|d
z  }|j                  |        ||      \  }}t        d |D              sJ t        d |D              sJ y# t
        $ r t        j                  d       Y w xY w)u-   해상도가 최소 기준 미달 → 경고.r   r  Nr  r  r  r  )r  i  r  r  z	small.pngc              3   $   K   | ]  }d |v  
 yw)u   최소 800pxNr8   .0ws     r   	<genexpr>zGTestValidateImageQuality.test_small_resolution_warns.<locals>.<genexpr>       91>Q&9   c              3   $   K   | ]  }d |v  
 yw)u   최소 400pxNr8   r  s     r   r	  zGTestValidateImageQuality.test_small_resolution_warns.<locals>.<genexpr>  r
  r  )r   r  r  r  r  r  r\   r  r  r  r  r  r  anyr  s	            r   test_small_resolution_warnsz4TestValidateImageQuality.test_small_resolution_warns  s    8	/! oobii//Cbhh/WX$2379999999999  	/KK-.	/s   
B B54B5c                 L   ddl m} 	 ddl}ddlm} j                  j                  ddd|j                        }j                  |      }|d	z  }|j                  |        ||      \  }}	t        d
 |	D              sJ y# t
        $ r t        j                  d       Y w xY w)u&   매우 어두운 이미지 → 경고.r   r  Nr  r  
   r  r  zdark.pngc              3   $   K   | ]  }d |v  
 yw)u   너무 어둡습니다Nr8   r  s     r   r	  zFTestValidateImageQuality.test_very_dark_image_warns.<locals>.<genexpr>  s     CQ+q0Cr  r   r  r  r  r  r  r\   r  r  r  r  r  r  r  
r   r   r  r   r  arrr  r  r  r  s
             r   test_very_dark_image_warnsz3TestValidateImageQuality.test_very_dark_image_warns  s    8	/!
 ii2}BHHEooc"#237C(CCCC  	/KK-.	/   
B B#"B#c                 L   ddl m} 	 ddl}ddlm} j                  j                  ddd|j                  	      }j                  |      }|d
z  }|j                  |        ||      \  }}	t        d |	D              sJ y# t
        $ r t        j                  d       Y w xY w)u:   매우 밝은 이미지 → 경고 (빈 이미지 의심).r   r  Nr  r        r  r  z
bright.pngc              3   $   K   | ]  }d |v  
 yw)u   너무 밝습니다Nr8   r  s     r   r	  zHTestValidateImageQuality.test_very_bright_image_warns.<locals>.<genexpr>  s     @!(A-@r  r  r  s
             r   test_very_bright_image_warnsz5TestValidateImageQuality.test_very_bright_image_warns  s    8	/!
 iiS-rxxHooc"%237@x@@@@  	/KK-.	/r  c                    ddl m} 	 ddl}ddlm} j                  dd|j                        }|j                  |j                  t              |j                  j                  dd	|j                        z   dd
      j                  |j                        }j!                  |      }|dz  }|j#                  |        ||      \  }}	t%        d |	D              sJ y# t
        $ r t        j                  d       Y w xY w)u3   색상이 매우 단조로운 이미지 → 경고.r   r  Nr  r  r     r  r     zmonotone.pngc              3   $   K   | ]  }d |v  
 yw)u   단조롭습니다Nr8   r  s     r   r	  zJTestValidateImageQuality.test_low_color_diversity_warns.<locals>.<genexpr>  s     ?'1,?r  )r   r  r  r  r  r  r\   r  fullr  clipastypeintr  r  shaper  r  r  r  s
             r   test_low_color_diversity_warnsz7TestValidateImageQuality.test_low_color_diversity_warns  s    8	/!
 ggmSg9ggcjjo		(9(9!Q		(JJAsSZZ[][c[cdooc"'237?h????  	/KK-.	/s   
C C?>C?)r5   r6   r7   r   r   r  r  r  r  r  r  r%  r8   r   r   r  r  u  s    ;:D :T :,T ,d ,   :D :T :"D4 DD D$AT Ad A$@t @ @r   r  )/r   r   syspathlibr   unittest.mockr   r   r\   r  insertrT  __file__parentr   r   r   r   r	   r
   r   r   r   r   r:   r`   ru   r   r   r   r   r   r   r   r   r  r  r  r   r1  r8  rC  rr  r  r  r  r  r8   r   r   <module>r,     s\    
  *  3tH~,,- .	 	 	 2 28T7 T7x> >2M M8! !E- E-Z& &6& &6)$ )$b- -85' 5'z)$ )$b/0 /0n' 'L< <B B6& &:1 1rA) A)RV( V(|Y, Y,B@ @P" "Jk@ k@r   