
    @i@                    ,   U d Z ddlmZ ddlZddlmc mZ ddl	m
Z ddlZddlZddlZddlZddlZdZ	  ej$                  de      Z ej(                  e      Zej,                  j/                  e       dZej:                  j=                  edud	e 
      Zh dZ h dZ!ddZ" G d d      Z# G d d      Z$ G d d      Z% G d d      Z& G d d      Z' G d d      Z( G d d      Z)y# e$ r#Z ej6                  d      ZeZded<   Y dZ[dZ[ww xY w)u	  
image-skill-router.py 유닛 테스트

테스트 범위:
  - route_skill: 작업 유형별 스킬 라우팅
  - route_skill: urgent=True 오버라이드
  - route_skill: count >= 10 강제 라우팅
  - route_skill: 알 수 없는 작업 유형 처리
  - route_skill: 출력 dict 구조 검증
  - get_skill_recommendation: 작업 설명 키워드 기반 스킬 추천
  - CLI 통합: subprocess 기반 end-to-end 실행

외부 의존성:
  - subprocess → CLI 통합 테스트
  - 나머지는 모듈 직접 호출
    )annotationsNz//home/jay/workspace/tools/image-skill-router.pyimage_skill_routerimage_skill_router_stubzException | None_IMPORT_ERRORu(   image-skill-router.py 임포트 실패: )reason>   gemini-imagehybrid-imagesatori-cardnews>   r   warnings
input_typealternativesrecommended_skillc                .    t        j                  | fi |S )u   route_skill 호출 헬퍼.)r   route_skill)	task_typekwargss     :/home/jay/workspace/tools/tests/test_image_skill_router.py_router   <   s    )))>v>>    c                  Z    e Zd ZdZej
                  j                  dg d      dd       Zy)TestBasicRoutingu4   각 작업 유형별 올바른 스킬 추천 검증.ztask_type, expected_skill))   광고 배너r	   )   카드뉴스r
   )   SNS 메인 이미지r   )u   블로그 썸네일r	   )u   A/B 테스트 변형r
   )   프리미엄 브랜딩r   )   인포그래픽r
   )	   비교표r
   )u   Meta 배너r	   )u   Google 정적 배너r
   )u   네이버 GFA Smartr
   )u   카카오 비즈보드r
   c           	        t        |      }|d   }||k(  }|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|d|d         d	z   d
|iz  }t        t        j                  |            dx}}y)u9   작업 유형에 따라 올바른 스킬이 추천된다.r   ==)z%(py1)s == %(py3)sexpected_skillpy1py3
task_type=z: expected z, got z
>assert %(py5)spy5N)
r   
@pytest_ar_call_reprcompare	_saferepr@py_builtinslocals_should_repr_global_name_format_assertmsgAssertionError_format_explanation)selfr   r!   result@py_assert0@py_assert2@py_format4@py_format6s           r   test_routing_by_task_typez*TestBasicRouting.test_routing_by_task_typeI   s    & 	")* 	
*n< 	
 	
*n 	
 	
 
	 + 	
 	
 
6	
 	
  /= 	
 	
 
	 /= 	
 	
   &&)7J0K/NP	
 	
 	
 	
 	
r   N)r   strr!   r7   returnNone)__name__
__module____qualname____doc__pytestmarkparametrizer6    r   r   r   r   F   s.    >[[#	
"
#"
r   r   c                  8    e Zd ZdZddZddZddZddZddZy)	TestUrgentModeu@   urgent=True 시 satori-cardnews 강제 및 경고 포함 검증.c                   t        dd      }|d   }d}||k(  }|slt        j                  d|fd||f      t        j                  |      t        j                  |      dz  }d	d
|iz  }t	        t        j
                  |            dx}x}}y)u;   urgent=True 이면 추천 스킬이 satori-cardnews 이다.r   Turgentr   r
   r   z%(py1)s == %(py4)sr#   py4assert %(py6)spy6Nr   r'   r(   r)   r.   r/   r0   r1   r2   @py_assert3r3   @py_format5@py_format7s          r   test_urgent_true_returns_satoriz.TestUrgentMode.test_urgent_true_returns_satorik   se    5)*?.??*.?????*.????*???.????????r   c                   t        dd      }|d   }t        |t              }|sddt        j                         v st        j                  t              rt        j                  t              ndt        j                  |      dt        j                         v st        j                  t              rt        j                  t              ndt        j                  |      dz  }t        t        j                  |            d	x}}|d   }t        |      }d
}||kD  }|st        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  }t        j                  d      dz   d|iz  }	t        t        j                  |	            d	x}x}x}}y	)u@   urgent=True 이면 warnings 리스트에 경고가 포함된다.r   TrE   r   5assert %(py5)s
{%(py5)s = %(py0)s(%(py2)s, %(py3)s)
}
isinstancelistpy0py2r$   r&   Nr   >z/%(py4)s
{%(py4)s = %(py0)s(%(py2)s)
} > %(py7)slenrW   rX   rI   py7u%   urgent 모드 경고가 없습니다.
>assert %(py9)spy9)r   rT   rU   r*   r+   r'   r,   r)   r.   r/   r\   r(   r-   )
r0   r1   @py_assert1@py_assert4r5   rN   @py_assert6@py_assert5@py_format8@py_format10s
             r   !test_urgent_true_includes_warningz0TestUrgentMode.test_urgent_true_includes_warningp   s"   5 ,3z,d33333333z333z333,333333d333d3333333333*%Ss%&SS&*SSS&SSSSSSsSSSsSSS%SSS&SSSSSS,SSSSSSSSr   c                `   t        d      }|d   }d}||k(  }|st        j                  d|fd||f      t        j                  |      t        j                  |      dz  }t        j                  d      dz   d	|iz  }t        t        j                  |            d
x}x}}t        dd      }|d   }d}||k(  }|slt        j                  d|fd||f      t        j                  |      t        j                  |      dz  }dd	|iz  }t        t        j                  |            d
x}x}}y
)uZ   원래 gemini-image가 추천될 작업도 urgent=True면 satori로 오버라이드된다.r   r   r   r   rG   rH   uZ   이 테스트는 '프리미엄 브랜딩'이 gemini-image를 반환해야 성립합니다.
>assert %(py6)srK   NTrE   r
   rJ   r   r'   r(   r)   r-   r.   r/   r0   normal_resultr2   rN   r3   rO   rP   urgent_results           r   +test_urgent_overrides_gemini_recommendationz:TestUrgentMode.test_urgent_overrides_gemini_recommendationv   s     7801 	
^ 	
1^C 	
 	
1^ 	
 	
 		 2 	
 	
 		 6D 	
 	
  i	
 	
 	
 	
 	

 7E01F5FF15FFFFF15FFFF1FFF5FFFFFFFFr   c                `   t        d      }|d   }d}||k(  }|st        j                  d|fd||f      t        j                  |      t        j                  |      dz  }t        j                  d      dz   d	|iz  }t        t        j                  |            d
x}x}}t        dd      }|d   }d}||k(  }|slt        j                  d|fd||f      t        j                  |      t        j                  |      dz  }dd	|iz  }t        t        j                  |            d
x}x}}y
)uZ   원래 hybrid-image가 추천될 작업도 urgent=True면 satori로 오버라이드된다.r   r   r	   r   rG   rH   uQ   이 테스트는 '광고 배너'이 hybrid-image를 반환해야 성립합니다.ri   rK   NTrE   r
   rJ   rj   rk   s           r   +test_urgent_overrides_hybrid_recommendationz:TestUrgentMode.test_urgent_overrides_hybrid_recommendation   s    /01 	
^ 	
1^C 	
 	
1^ 	
 	
 		 2 	
 	
 		 6D 	
 	
  `	
 	
 	
 	
 	
 t<01F5FF15FFFFF15FFFF1FFF5FFFFFFFFr   c                   t        dd      }|d   }d}||k(  }|slt        j                  d|fd||f      t        j                  |      t        j                  |      dz  }d	d
|iz  }t	        t        j
                  |            dx}x}}y)u?   urgent=False(기본값)이면 강제 오버라이드가 없다.r   FrE   r   r	   r   rG   rH   rJ   rK   NrL   rM   s          r   'test_urgent_false_does_not_force_satoriz6TestUrgentMode.test_urgent_false_does_not_force_satori   sa    6)*<n<*n<<<<*n<<<*<<<n<<<<<<<r   Nr8   r9   )	r:   r;   r<   r=   rQ   rg   rn   rp   rr   rA   r   r   rC   rC   h   s"    J@
T
GG=r   rC   c                  8    e Zd ZdZddZddZddZddZddZy)	TestBulkGenerationu>   count 값에 따른 satori 강제 및 정상 라우팅 검증.c                   t        dd      }|d   }d}||k(  }|slt        j                  d|fd||f      t        j                  |      t        j                  |      dz  }d	d
|iz  }t	        t        j
                  |            dx}x}}y)u3   count >= 10 이면 satori-cardnews가 강제된다.r   
   countr   r
   r   rG   rH   rJ   rK   NrL   rM   s          r   test_count_10_forces_satoriz.TestBulkGeneration.test_count_10_forces_satori   se    r2)*?.??*.?????*.????*???.????????r   c                   t        dd      }|d   }d}||k(  }|slt        j                  d|fd||f      t        j                  |      t        j                  |      dz  }d	d
|iz  }t	        t        j
                  |            dx}x}}y)uB   count=100 같은 대량 생성도 satori-cardnews를 반환한다.r   d   rx   r   r
   r   rG   rH   rJ   rK   NrL   rM   s          r   test_count_100_forces_satoriz/TestBulkGeneration.test_count_100_forces_satori   sf    0<)*?.??*.?????*.????*???.????????r   c                   t        dd      }|d   }d}||k(  }|slt        j                  d|fd||f      t        j                  |      t        j                  |      dz  }d	d
|iz  }t	        t        j
                  |            dx}x}}y)u7   count=9 이면 정상 라우팅 결과를 반환한다.r   	   rx   r   r	   r   rG   rH   rJ   rK   NrL   rM   s          r   test_count_9_normal_routingz.TestBulkGeneration.test_count_9_normal_routing   sa    q1)*<n<*n<<<<*n<<<*<<<n<<<<<<<r   c                   t        dd      }|d   }d}||k(  }|slt        j                  d|fd||f      t        j                  |      t        j                  |      dz  }d	d
|iz  }t	        t        j
                  |            dx}x}}y)uA   count=1(기본값)이면 정상 라우팅 결과를 반환한다.r      rx   r   r
   r   rG   rH   rJ   rK   NrL   rM   s          r   test_count_1_normal_routingz.TestBulkGeneration.test_count_1_normal_routing   se    a0)*?.??*.?????*.????*???.????????r   c                   t        d      }|d   }d}||k(  }|slt        j                  d|fd||f      t        j                  |      t        j                  |      dz  }dd|iz  }t	        t        j
                  |            d	x}x}}y	)
uA   count를 명시하지 않으면 정상 라우팅이 적용된다.r   r   r   r   rG   rH   rJ   rK   NrL   rM   s          r   !test_count_default_normal_routingz4TestBulkGeneration.test_count_default_normal_routing   s`    ./)*<n<*n<<<<*n<<<*<<<n<<<<<<<r   Nrs   )	r:   r;   r<   r=   rz   r}   r   r   r   rA   r   r   ru   ru      s!    H@
@
=
@
=r   ru   c                  0    e Zd ZdZddZddZddZddZy)TestUnknownTaskTypeu6   알 수 없는 작업 유형 입력 시 처리 검증.c                   t        d      }t        |t              }|s!t        j                  d      dz   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)	u;   알 수 없는 작업 유형에서도 dict를 반환한다.u*   완전히 알 수 없는 작업 유형 xyzu   dict를 반환해야 합니다.7
>assert %(py4)s
{%(py4)s = %(py0)s(%(py1)s, %(py2)s)
}rT   r1   dictrW   r#   rX   rI   N)r   rT   r   r'   r-   r*   r+   r,   r)   r.   r/   r0   r1   rN   rO   s       r   #test_unknown_task_type_returns_dictz7TestUnknownTaskType.test_unknown_task_type_returns_dict   s    DE&$'J'JJ)JJJJJJJzJJJzJJJJJJ&JJJ&JJJJJJ$JJJ$JJJ'JJJJJJr   c                   t        d      }|d   }g }d}||u }|}|s
|t        v }|}|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  }	|j                  |	       |st        j                  d
fd|t        f      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dz  }
dd|
iz  }|j                  |       t        j                  |d      i z  }t        j                  d|d      dz   d|iz  }t        t        j                  |            dx}x}x}x}}y)u]   알 수 없는 작업 유형이면 recommended_skill이 None이거나 유효한 스킬이다.u   알수없음abcXYZ123r   N)is)z%(py2)s is %(py5)sskill)rX   r&   z%(py7)sr^   in)z%(py9)s in %(py11)sVALID_SKILLS)r`   py11z%(py13)spy13r   u   반환된 스킬 u-   이 None도 유효한 스킬도 아닙니다.z
>assert %(py16)spy16)r   r   r'   r(   r*   r+   r,   r)   append_format_boolopr-   r.   r/   )r0   r1   r   ra   rb   rN   r2   @py_assert10r5   re   @py_format12@py_format14@py_format15@py_format17s                 r   2test_unknown_task_type_returns_none_or_valid_skillzFTestUnknownTaskType.test_unknown_task_type_returns_none_or_valid_skill   s   /0*+	
 	
u} 	
 5 	
 	
 	
u 	
 	
	6	
 	
   	
 	
 		  	
 	
 		  	
 	
 	
	6	
		
 	
 	
 	
	6	
 	
  !& 	
 	
 		 !& 	
 	
	6	
 	
  *6 	
 	
 		 *6 	
 	
 	
	6	
		
 	
 	
   y(UV	
 	
 	
 	
 	
 	
r   c                   	 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}y# t        t        f$ r Y yw xY w)u3   빈 문자열 작업 유형도 처리 가능하다. 5assert %(py4)s
{%(py4)s = %(py0)s(%(py1)s, %(py2)s)
}rT   r1   r   r   N)r   rT   r   r*   r+   r'   r,   r)   r.   r/   
ValueErrorKeyErrorr   s       r   test_empty_string_task_typez/TestUnknownTaskType.test_empty_string_task_type   s    	BZFfd++++++++:+++:++++++f+++f++++++d+++d++++++++++H% 		s   D+D. .E ?E c                   	 t        d      }t        |t              rt        t	        |j                               z
  }| }|s~t        j                  d|       dz   ddt        j                         v st        j                  |      rt        j                  |      ndiz  }t        t        j                  |            d}yy# t        t        t         f$ r Y yw xY w)u^   알 수 없는 작업 유형에서 dict를 반환할 경우 필수 키가 모두 포함된다.u   알수없음XYZ   필수 키 누락: 
>assert not %(py0)srW   missingN)r   rT   r   REQUIRED_KEYSsetkeysr'   r-   r*   r+   r,   r)   r.   r/   r   r   
SystemExit)r0   r1   r   ra   @py_format2s        r   8test_unknown_task_type_has_required_keys_if_returns_dictzLTestUnknownTaskType.test_unknown_task_type_has_required_keys_if_returns_dict   s    	-.F&$''#fkkm*<<"{C{CC&9'$CCCCCCC7CCC7CCCCCC ( Hj1 		s   C C CCNrs   )r:   r;   r<   r=   r   r   r   r   rA   r   r   r   r      s    @K

r   r   c                      e Zd ZdZej
                  dd       ZddZddZddZ	ddZ
ddZddZdd	Zdd
Zej                  j!                  dg d      dd       Zy)TestOutputStructureu8   route_skill 반환 dict의 구조 및 값 타입 검증.c                    t        d      S )u+   테스트용 샘플 결과 (광고 배너).r   )r   )r0   s    r   sample_resultz!TestOutputStructure.sample_result   s     o&&r   c                N   t         t        |j                               z
  }| }|s~t        j                  d|       dz   ddt        j                         v st        j                  |      rt        j                  |      ndiz  }t        t        j                  |            d}y)u1   반환 dict에 필수 키가 모두 존재한다.r   r   rW   r   N)r   r   r   r'   r-   r*   r+   r,   r)   r.   r/   )r0   r   r   ra   r   s        r   test_required_keys_presentz.TestOutputStructure.test_required_keys_present   se    #m&8&8&:";;{;{;;1';;;;;;;7;;;7;;;;;;r   c                t   |d   }|t         v }|st        j                  d|fd|t         f      t        j                  |      dt	        j
                         v st        j                  t               rt        j                  t               nddz  }dd|iz  }t        t        j                  |            dx}}y)	u7   recommended_skill 값이 유효한 스킬 이름이다.r   r   )z%(py1)s in %(py3)sr   r"   assert %(py5)sr&   N)	r   r'   r(   r)   r*   r+   r,   r.   r/   )r0   r   r2   r3   r4   r5   s         r   test_recommended_skill_is_validz3TestOutputStructure.test_recommended_skill_is_valid   sd    01A1\AAAA1\AAA1AAAAAA\AAA\AAAAAAAr   c                   |d   }t        |t              }|sddt        j                         v st	        j
                  t               rt	        j                  t               ndt	        j                  |      dt        j                         v st	        j
                  t              rt	        j                  t              ndt	        j                  |      dz  }t        t	        j                  |            dx}}|d   }t        |      }d}||kD  }|st	        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  }t	        j                  d      dz   d|iz  }	t        t	        j                  |	            dx}x}x}}y)u   reason 값이 문자열이다.r   rS   rT   r7   rV   Nr   rY   r[   r\   r]   u!   reason이 빈 문자열입니다.r_   r`   )rT   r7   r*   r+   r'   r,   r)   r.   r/   r\   r(   r-   )
r0   r   ra   rb   r5   rN   rc   rd   re   rf   s
             r   test_reason_is_stringz)TestOutputStructure.test_reason_is_string   s   '17z1377777777z777z7771777777377737777777777 *Ts*+TaT+a/TTT+aTTTTTTsTTTsTTT*TTT+TTTaTTT1TTTTTTTTr   c                   |d   }t        |t              }|sddt        j                         v st	        j
                  t               rt	        j                  t               ndt	        j                  |      dt        j                         v st	        j
                  t              rt	        j                  t              ndt	        j                  |      dz  }t        t	        j                  |            dx}}y)u$   alternatives 값이 리스트이다.r   rS   rT   rU   rV   N	rT   rU   r*   r+   r'   r,   r)   r.   r/   r0   r   ra   rb   r5   s        r   test_alternatives_is_listz-TestOutputStructure.test_alternatives_is_list   s    '7>z7>>>>>>>>z>>>z>>>7>>>>>>>>>>>>>>>>>>>r   c                   |d   D ]  }|t         v }|st        j                  d|fd|t         f      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dz  }t        j                  d|      dz   d	|iz  }t        t        j                  |            d
} y
)u<   alternatives의 각 항목이 유효한 스킬 이름이다.r   r   z%(py0)s in %(py2)saltr   rW   rX   u#   유효하지 않은 대안 스킬: z
>assert %(py4)srI   N)
r   r'   r(   r*   r+   r,   r)   r-   r.   r/   )r0   r   r   ra   @py_format3rO   s         r   'test_alternatives_contains_valid_skillsz;TestOutputStructure.test_alternatives_contains_valid_skills   s     0 	VC,&UUU3,UUUUUU3UUU3UUUUUU,UUU,UUUU*McW(UUUUUUU	Vr   c                   |d   }t        |t              }|sddt        j                         v st	        j
                  t               rt	        j                  t               ndt	        j                  |      dt        j                         v st	        j
                  t              rt	        j                  t              ndt	        j                  |      dz  }t        t	        j                  |            dx}}y)u    warnings 값이 리스트이다.r   rS   rT   rU   rV   Nr   r   s        r   test_warnings_is_listz)TestOutputStructure.test_warnings_is_list   s    '
3:z3T::::::::z:::z:::3::::::T:::T::::::::::r   c                   |d   }t        |t              }|sddt        j                         v st	        j
                  t               rt	        j                  t               ndt	        j                  |      dt        j                         v st	        j
                  t              rt	        j                  t              ndt	        j                  |      dz  }t        t	        j                  |            dx}}y)u"   input_type 값이 문자열이다.r   rS   rT   r7   rV   N)	rT   r7   r*   r+   r'   r,   r)   r.   r/   r   s        r   test_input_type_is_stringz-TestOutputStructure.test_input_type_is_string  s    '5;z5s;;;;;;;;z;;;z;;;5;;;;;;s;;;s;;;;;;;;;;r   c                4   |d   }|d   }||v}|st        j                  d|fd||f      t        j                  |      t        j                  |      dz  }t        j                  d      dz   d|iz  }t	        t        j
                  |            d	x}x}}y	)
u<   recommended_skill이 alternatives에 포함되지 않는다.r   r   )not in)z%(py1)s not in %(py4)srH   uB   추천된 스킬이 대안 목록에도 포함되어 있습니다.ri   rK   N)r'   r(   r)   r-   r.   r/   )r0   r   r2   rN   r3   rO   rP   s          r   *test_recommended_skill_not_in_alternativesz>TestOutputStructure.test_recommended_skill_not_in_alternatives  s    01 	
~9V 	
19VV 	
 	
19V 	
 	
 		 2 	
 	
 		 :W 	
 	
  Q	
 	
 	
 	
 	
 	
r   r   )r   r   r   r   r   c                j   t        |      }t        t        |j                               z
  }| }|st	        j
                  d|d|       dz   ddt        j                         v st	        j                  |      rt	        j                  |      ndiz  }t        t	        j                  |            d}y)u@   다양한 작업 유형에서 모두 필수 키가 존재한다.r%   u   : 필수 키 누락: r   rW   r   N)r   r   r   r   r'   r-   r*   r+   r,   r)   r.   r/   )r0   r   r1   r   ra   r   s         r   &test_all_task_types_have_required_keysz:TestOutputStructure.test_all_task_types_have_required_keys  st     	"#fkkm"44{T{TTj5J7)TTTTTTT7TTT7TTTTTTr   N)r8   r   )r   r   r8   r9   )r   r7   r8   r9   )r:   r;   r<   r=   r>   fixturer   r   r   r   r   r   r   r   r   r?   r@   r   rA   r   r   r   r      sq    B^^' '<
BU
?V
;<
 [[aU	Ur   r   c                  8    e Zd ZdZddZddZddZddZddZy)	TestGetSkillRecommendationuN   get_skill_recommendation 함수: 작업 설명 키워드 기반 추천 검증.c                z   t         j                  d      }d}||k(  }|st        j                  d|fd||f      dt	        j
                         v st        j                  |      rt        j                  |      ndt        j                  |      dz  }dd|iz  }t        t        j                  |            d	x}}y	)
u/   '메타 광고 배너 제작' → hybrid-image.u   메타 광고 배너 제작r	   r   z%(py0)s == %(py3)sr1   rW   r$   r   r&   N
r   get_skill_recommendationr'   r(   r*   r+   r,   r)   r.   r/   r0   r1   r3   ra   r4   r5   s         r   (test_meta_ad_banner_returns_hybrid_imagezCTestGetSkillRecommendation.test_meta_ad_banner_returns_hybrid_image!  sl    #<<=Z[''v''''v''''''v'''v''''''''''r   c                z   t         j                  d      }d}||k(  }|st        j                  d|fd||f      dt	        j
                         v st        j                  |      rt        j                  |      ndt        j                  |      dz  }dd|iz  }t        t        j                  |            d	x}}y	)
u1   '보험 카드뉴스 제작' → satori-cardnews.u   보험 카드뉴스 제작r
   r   r   r1   r   r   r&   Nr   r   s         r   &test_insurance_cardnews_returns_satorizATestGetSkillRecommendation.test_insurance_cardnews_returns_satori&  so    #<<=YZ**v*****v*******v***v***********r   c                z   t         j                  d      }d}||k(  }|st        j                  d|fd||f      dt	        j
                         v st        j                  |      rt        j                  |      ndt        j                  |      dz  }dd|iz  }t        t        j                  |            d	x}}y	)
u(   'SNS 홍보 이미지' → gemini-image.u   SNS 홍보 이미지r   r   r   r1   r   r   r&   Nr   r   s         r   #test_sns_promo_image_returns_geminiz>TestGetSkillRecommendation.test_sns_promo_image_returns_gemini+  sl    #<<=ST''v''''v''''''v'''v''''''''''r   c                n   t         j                  d      }t        |t              }|sddt	        j
                         v st        j                  t              rt        j                  t              nddt	        j
                         v st        j                  |      rt        j                  |      nddt	        j
                         v st        j                  t              rt        j                  t              ndt        j                  |      dz  }t        t        j                  |            d}y)u   반환값이 문자열이다.r   r   rT   r1   r7   r   N)r   r   rT   r7   r*   r+   r'   r,   r)   r.   r/   r   s       r   test_return_type_is_stringz5TestGetSkillRecommendation.test_return_type_is_string0  s    #<<_M&#&&&&&&&&z&&&z&&&&&&&&&&&&&&&&&#&&&#&&&&&&&&&&r   c                   t         j                  d      }|t        v }|st        j                  d|fd|t        f      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dz  }dd|iz  }t        t        j                  |            d	}y	)
u+   반환값이 유효한 스킬 이름이다.u   카드뉴스 만들기r   r   r1   r   r   zassert %(py4)srI   N)r   r   r   r'   r(   r*   r+   r,   r)   r.   r/   )r0   r1   ra   r   rO   s        r    test_return_value_is_valid_skillz;TestGetSkillRecommendation.test_return_value_is_valid_skill5  s{    #<<=UV%%%%v%%%%%%v%%%v%%%%%%%%%%%%%%%%r   Nrs   )	r:   r;   r<   r=   r   r   r   r   r   rA   r   r   r   r     s    X(
+
(
'
&r   r   c                  L    e Zd ZdZeZd
dZddZddZddZ	ddZ
ddZddZy	)TestCLIIntegrationu;   subprocess로 CLI 직접 실행하여 통합 동작 검증.c                t    t         j                  | j                  g|z   }t        j                  |fdddd|S )u9   CLI를 subprocess로 실행하고 결과를 반환한다.T   )capture_outputtexttimeout)sys
executable_SCRIPT
subprocessrun)r0   argsr   cmds       r   _runzTestCLIIntegration._runE  sE    ~~t||,t3~~
	

 
 	
r   c                4   | j                  ddg      }|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  }t        j                  d|j                   d	|j                   d
|j                         dz   d|iz  }t        t        j                  |            dx}x}}y)u>   --type '광고 배너' 실행 시 exit code 0을 반환한다.--typer   r   r   z2%(py2)s
{%(py2)s = %(py0)s.returncode
} == %(py5)sr1   rW   rX   r&   
exit code 	
stdout: 	
stderr: 
>assert %(py7)sr^   N)r   
returncoder'   r(   r*   r+   r,   r)   r-   stdoutstderrr.   r/   r0   r1   ra   rb   rN   r5   re   s          r   test_basic_run_exits_zeroz,TestCLIIntegration.test_basic_run_exits_zeroP  s    Ho67   	
A 	
 A% 	
 	
 A 	
 	
	6	
 	
   	
 	
 		  	
 	
 		 ! 	
 	
 		 %& 	
 	
  **+:fmm_Jv}}o^	
 	
 	
 	
 	
 	
r   c                R   | j                  g 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  }t        j                  d|j                   d|j                         d	z   d
|iz  }t        t        j                  |            dx}x}}	 t        j                  |j                        }t%        t&              }|s!t        j                  d      dz   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# t        j                  $ r0}t!        j"                  d| d|j                         Y d}~ud}~ww xY w)u;   --json 플래그 사용 시 출력이 유효한 JSON이다.r   r   --jsonr   r   r   r1   r   r   r   r   r^   Nu   JSON 파싱 실패: r   u'   JSON 최상위가 dict여야 합니다.r   rT   parsedr   r   )r   r   r'   r(   r*   r+   r,   r)   r-   r   r.   r/   jsonloadsr   JSONDecodeErrorr>   failrT   r   )
r0   r1   ra   rb   rN   r5   re   r   erO   s
             r   test_json_output_is_parseablez0TestCLIIntegration.test_json_output_is_parseableW  s   @A   	
A 	
 A% 	
 	
 A 	
 	
	6	
 	
   	
 	
 		  	
 	
 		 ! 	
 	
 		 %& 	
 	
  **+:fmm_E	
 	
 	
 	
 	
	OZZ.F &$'R'RR)RRRRRRRzRRRzRRRRRR&RRR&RRRRRR$RRR$RRR'RRRRRR ## 	OKK.qcFMM;LMNN	Os   I# #J&6%J!!J&c                B   | j                  g 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}}t        j                  |j                        }t        t        |j                               z
  }| }|s~t        j                   d
|       dz   ddt	        j
                         v st        j                  |      rt        j                  |      ndiz  }	t        t        j                  |	            d	}y	)u3   --json 출력에 필수 키가 모두 포함된다.r   r   r   r   r1   r   assert %(py7)sr^   Nu"   JSON 출력에 필수 키 누락: r   rW   r   )r   r   r'   r(   r*   r+   r,   r)   r.   r/   r   r   r   r   r   r   r-   )
r0   r1   ra   rb   rN   r5   re   r   r   r   s
             r   "test_json_output_has_required_keysz5TestCLIIntegration.test_json_output_has_required_keysc  s    @A  %A% A%%%% A%%%%%%v%%%v%%% %%%A%%%%%%%FMM*#fkkm"44{J{JJ@	JJJJJJJ7JJJ7JJJJJJr   c                   | j                  g 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  }t        j                  d|j                   d|j                         d	z   d
|iz  }t        t        j                  |            dx}x}}t        j                  |j                        }|d   }d}||k(  }	|	st        j                  d|	fd||f      t        j                  |      t        j                  |      dz  }
t        j                  d|d   d      dz   d|
iz  }t        t        j                  |            dx}x}	}y)u>   --urgent 플래그 사용 시 satori-cardnews가 추천된다.)r   r   z--urgentr   r   r   r   r1   r   r   r   r   r^   Nr   r
   rG   rH   u%   urgent 모드에서 satori가 아닌 u
    추천됨ri   rK   )r   r   r'   r(   r*   r+   r,   r)   r-   r   r.   r/   r   r   r   r0   r1   ra   rb   rN   r5   re   r   r2   r3   rO   rP   s               r   "test_urgent_flag_recommends_satoriz5TestCLIIntegration.test_urgent_flag_recommends_satorik  s   LM   	
A 	
 A% 	
 	
 A 	
 	
	6	
 	
   	
 	
 		  	
 	
 		 ! 	
 	
 		 %& 	
 	
  **+:fmm_E	
 	
 	
 	
 	
 FMM*)* 	
.? 	
*.?? 	
 	
*.? 	
 	
 		 + 	
 	
 		 /@ 	
 	
  4F;N4O3RR\]	
 	
 	
 	
 	
 	
r   c                   | j                  g       }|j                  }d}||k7  }|st        j                  d|fd||f      dt	        j
                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      dz  }t        j                  d      dz   d|iz  }t        t        j                  |            d	x}x}}y	)
u>   인수 없이 실행하면 non-zero exit code를 반환한다.r   )!=)z2%(py2)s
{%(py2)s = %(py0)s.returncode
} != %(py5)sr1   r   u@   인수 없이 실행했는데 exit code 0이 반환됐습니다.r   r^   N)r   r   r'   r(   r*   r+   r,   r)   r-   r.   r/   r   s          r   test_no_args_exits_nonzeroz-TestCLIIntegration.test_no_args_exits_nonzerov  s    2   	
A 	
 A% 	
 	
 A 	
 	
	6	
 	
   	
 	
 		  	
 	
 		 ! 	
 	
 		 %& 	
 	
  O	
 	
 	
 	
 	
 	
r   c                   | j                  g 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}}t        j                  |j                        }|d
   }d}||k(  }	|	slt        j                  d|	fd||f      t        j                  |      t        j                  |      dz  }
dd|
iz  }t        t        j                  |            d	x}x}	}y	)uK   CLI에서 '카드뉴스' 유형 실행 시 satori-cardnews가 추천된다.)r   r   r   r   r   r   r1   r   r  r^   Nr   r
   rG   rH   rJ   rK   )r   r   r'   r(   r*   r+   r,   r)   r.   r/   r   r   r   r	  s               r   ,test_cardnews_type_recommends_satori_via_cliz?TestCLIIntegration.test_cardnews_type_recommends_satori_via_cli}  s    ?@  %A% A%%%% A%%%%%%v%%%v%%% %%%A%%%%%%%FMM*)*?.??*.?????*.????*???.????????r   N)r   z	list[str]r8   zsubprocess.CompletedProcessrs   )r:   r;   r<   r=   _MODULE_PATHr   r   r   r  r  r
  r  r  rA   r   r   r   r   @  s0    EG	


SK	

@r   r   )r   r7   r8   r   )*r=   
__future__r   builtinsr*   _pytest.assertion.rewrite	assertionrewriter'   importlib.utilutil_ilur   r   r   typesr>   r  spec_from_file_location_specmodule_from_specr   loaderexec_moduler   	Exceptionexc
ModuleType__annotations__r?   skipif
pytestmarkr   r   r   r   rC   ru   r   r   r   r   rA   r   r   <module>r%     s<  " #       
   A(D(()=|LE...u5	LL/0
 M[[5m_E   
 C Z?
 
D'= '=^= =D   P5U 5Uz& &DB@ B@w  *)))*CD&)M#))*s   A C+ +D0DD