
    miP                     v   d Z ddlZddlmc mZ ddlZddlZddl	Z	ddl
mZ ddlmZ ddlmZmZmZ ddlZddlmZmZmZmZmZmZmZ ej6                  deeef   fd       Zej6                  d	eeef   deeef   fd
       Zej6                  deeef   dedefd       Z ej6                  defd       Z!ej6                  defd       Z"ej6                  def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,y)#u   
TDD RED 단계: eval runner 테스트 파일
run_evals.py 구현이 없으므로 모든 테스트는 ImportError 또는 FAIL 상태여야 합니다.
    N)Path)Any)	MagicMock	mock_openpatch)check_forbiddencheck_routingevaluate_responsegenerate_reportget_skill_listkeyword_match
load_evalsreturnc                      dddddgg dS )u;   단일 eval 케이스 픽스처 (evals.json 구조 반영).   uw   InsuWiki Google RSA 광고를 처음부터 만들어주세요. 헤드라인 10개와 설명 3개를 만들어주세요.u4  Google RSA 규격(헤드라인 30자 이하, 설명 90자 이하)을 준수하며 최소 10개의 헤드라인과 3개의 설명을 제공해야 합니다. 헤드라인은 독립적으로 의미가 통해야 하며 키워드 중심, 혜택 중심, CTA 중심 헤드라인이 고루 포함되어야 합니다.z/Checks all headlines are 30 characters or fewerz.Provides at least one keyword-focused headlineidpromptexpected_output
assertionsfiles r       7/home/jay/workspace/tools/eval-runner/test_run_evals.pysample_eval_caser      s/      LZ
 ><
  r   r   c                     d| ddddgg dgdS )u#   evals.json 전체 구조 픽스처.ad-creative   u0   Meta 피드 광고 소재를 만들어주세요.uw   Meta 광고 규격에 맞게 기본 텍스트, 헤드라인, 설명을 각 3가지 변형으로 제공해야 합니다.z@Checks primary text hooks appear within the first 125 charactersr   
skill_nameevalsr   )r   s    r   sample_evals_json_contentr"   2   s3     $L $]ab	
 r   r"   tmp_pathc                     |dz  dz  dz  }|j                  d       |dz  }|j                  t        j                  | d      d	
       |S )u:   임시 evals.json 파일 경로를 반환하는 픽스처.skillsr   r!   Tparents
evals.jsonFensure_asciiutf-8encoding)mkdir
write_textjsondumps)r"   r#   	skill_dir
evals_files       r   evals_json_pathr4   D   sV     8#m3g=IOODO!\)J$**%>US^efr   c                       	 y)u3   ad-creative 스킬의 boundary 문자열 픽스처.u   For campaign strategy, budget allocation, or platform selection, → paid-ads. For social media content strategy, → social-content.r   r   r   r   ad_creative_boundaryr6   N   s    	Ar   c                       	 y)u9   정상 통과 LLM 응답 픽스처 (헤드라인 포함).u$  Google RSA 헤드라인 목록:
1. 보험 약관 3분에 이해하기 (15자)
2. 내 보험 제대로 알고 있나요? (16자)
3. 복잡한 약관 쉽게 풀어드립니다 (16자)
설명: 보험 약관 핵심 내용만 쉽게 정리. 청구 누락 없이 내 보험을 100% 활용하세요.r   r   r   r   llm_response_passr8   W   s    	yr   c                       y)u8   실패하는 LLM 응답 픽스처 (헤드라인 없음).uY   죄송합니다. 광고 캠페인 예산 전략은 paid-ads 스킬을 이용해주세요.r   r   r   r   llm_response_failr:   c   s     gr   c                   f    e Zd ZdZdedeeef   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dZy)TestLoadEvalsu.   evals.json 파일 로딩 및 파싱 테스트.r4   r"   r   Nc                 v   t        |j                  j                  j                        }t        d|      5  t        d      }d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}}|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}||k(  }|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  }dd|iz  }t        t	        j                  |            dx}
x}x}}y# 1 sw Y   nxY w)uP   evals.json을 정상적으로 읽어 올바른 구조를 반환하는지 확인.run_evals.SKILLS_BASE_PATHr   Nr    ==z%(py1)s == %(py4)spy1py4assert %(py6)spy6r!   z5assert %(py5)s
{%(py5)s = %(py0)s(%(py2)s, %(py3)s)
}
isinstancelist)py0py2py3py5r   z0%(py4)s
{%(py4)s = %(py0)s(%(py2)s)
} == %(py7)slenrI   rJ   rD   py7assert %(py9)spy9)strparentr   r   
@pytest_ar_call_reprcompare	_safereprAssertionError_format_explanationrG   rH   @py_builtinslocals_should_repr_global_namerN   )selfr4   r"   	base_pathresult@py_assert0@py_assert3@py_assert2@py_format5@py_format7@py_assert1@py_assert4@py_format6@py_assert6@py_assert5@py_format8@py_format10s                    r   'test_load_evals_returns_valid_structurez5TestLoadEvals.test_load_evals_returns_valid_structureq   s    ..55<<=	/; 	/.F	/ l#4}4#}4444#}444#444}4444444 /0z/400000000z000z000/000000400040000000000'?(s?#(q(#q((((#q((((((s(((s(((?(((#(((q(((((((	/ 	/s   J..J8c                 2   t        |j                  j                  j                        }t        d|      5  t        d      }ddd       d   d   }d}||v }|st	        j
                  d|fd||f      t	        j                  |      d	t        j                         v st	        j                  |      rt	        j                  |      nd	d
z  }dd|iz  }t        t	        j                  |            dx}}d}||v }|st	        j
                  d|fd||f      t	        j                  |      d	t        j                         v st	        j                  |      rt	        j                  |      nd	d
z  }dd|iz  }t        t	        j                  |            dx}}d}||v }|st	        j
                  d|fd||f      t	        j                  |      d	t        j                         v st	        j                  |      rt	        j                  |      nd	d
z  }dd|iz  }t        t	        j                  |            dx}}d}||v }|st	        j
                  d|fd||f      t	        j                  |      d	t        j                         v st	        j                  |      rt	        j                  |      nd	d
z  }dd|iz  }t        t	        j                  |            dx}}d}||v }|st	        j
                  d|fd||f      t	        j                  |      d	t        j                         v st	        j                  |      rt	        j                  |      nd	d
z  }dd|iz  }t        t	        j                  |            dx}}y# 1 sw Y   LxY w)u`   각 eval 케이스에 id, prompt, expected_output, assertions, files 필드가 있는지 확인.r>   r   Nr!   r   r   inz%(py1)s in %(py3)s
first_evalrC   rK   assert %(py5)srL   r   r   r   r   )rS   rT   r   r   rU   rV   rW   rZ   r[   r\   rX   rY   )	r]   r4   r^   r_   rq   r`   rb   @py_format4rg   s	            r   "test_load_evals_parses_eval_fieldsz0TestLoadEvals.test_load_evals_parses_eval_fields   s   
 ..55<<=	/; 	/.F	/ G_Q'
!tz!!!!tz!!!t!!!!!!z!!!z!!!!!!!%x:%%%%x:%%%x%%%%%%:%%%:%%%%%%% . J.... J... ......J...J.......)|z))))|z)))|))))))z)))z)))))))$w*$$$$w*$$$w$$$$$$*$$$*$$$$$$$	/ 	/s   NNr#   c                 v   |dz  dz  dz  }|j                  d       |dz  }|j                  dd	       t        |dz  d
z        }t        dt        |            5  t	        j
                  t        j                  t        f      5  t        d       ddd       ddd       y# 1 sw Y   xY w# 1 sw Y   yxY w)uH   잘못된 JSON 파일일 때 적절한 에러가 발생하는지 확인.r%   z	bad-skillr!   Tr&   r(   z{invalid json: }r+   r,   z..r>   N)
r.   r/   rS   r   pytestraisesr0   JSONDecodeError
ValueErrorr   )r]   r#   r2   bad_jsonr^   s        r   )test_load_evals_invalid_json_raises_errorz7TestLoadEvals.test_load_evals_invalid_json_raises_error   s    x'+5?	%|+.A8+d23	/X? 	( 4 4jAB (;'(	( 	(( (	( 	(s$   *B/B#B/#B,	(B//B8c                    |dz  dz  dz  }|j                  d       |dz  }|j                  t        j                  dg dd	      d
       t	        dt        |            5  t        d      }ddd       d   }g }||k(  }|slt        j                  d|fd||f      t        j                  |      t        j                  |      dz  }dd|iz  }	t        t        j                  |	            dx}x}}y# 1 sw Y   xY w)uH   빈 evals 배열을 가진 evals.json을 처리할 수 있는지 확인.r%   zempty-skillr!   Tr&   r(   r   Fr)   r+   r,   r>   Nr?   rA   rB   rE   rF   )r.   r/   r0   r1   r   rS   r   rU   rV   rW   rX   rY   )
r]   r#   r2   empty_evalsr_   r`   ra   rb   rc   rd   s
             r   !test_load_evals_empty_evals_arrayz/TestLoadEvals.test_load_evals_empty_evals_array   s    x'-7'A	%,.JJmbAPUV 	 	

 /X? 	/.F	/ g$"$"$$$$"$$$$$$"$$$$$$$	/ 	/s   #C88Dc                     t        dd      5  t        j                  t        t        t
        f      5  t        d       ddd       ddd       y# 1 sw Y   xY w# 1 sw Y   yxY w)uO   존재하지 않는 스킬명을 전달하면 에러가 발생하는지 확인.r>   z/nonexistent/pathznonexistent-skillN)r   rw   rx   FileNotFoundErrorOSErrorrz   r   r]   s    r   .test_load_evals_nonexistent_skill_raises_errorz<TestLoadEvals.test_load_evals_nonexistent_skill_raises_error   sZ    /1DE 	0 17JGH 0./0	0 	00 0	0 	0s!   %AAAA	AA$r   N)__name__
__module____qualname____doc__r   dictrS   r   rl   ru   r|   r   r   r   r   r   r<   r<   n   sp    8)) $(S>) 
	)%% 
% 
($ 
(4 
(%$ %4 %0r   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)	TestKeywordMatchu   keyword_match 함수 테스트.Nc                    d}d}t        ||      \  }}}d}||u }|st        j                  d|fd||f      dt        j                         v st        j
                  |      rt        j                  |      ndt        j                  |      dz  }dd	|iz  }	t        t        j                  |	            d
x}}d}
|
|v }|st        j                  d|fd|
|f      t        j                  |
      dt        j                         v st        j
                  |      rt        j                  |      nddz  }dd	|iz  }	t        t        j                  |	            d
x}
}y
)uK   한글 키워드가 LLM 응답에 포함되어 있을 때 pass=True 반환.uX   헤드라인 10개를 작성해 드리겠습니다: '보험 약관 3분에 이해하기'u7   최소 10개의 헤드라인을 제공해야 합니다.Tisz%(py0)s is %(py3)spassedrI   rK   rs   rL   N   헤드라인rn   rp   matchedrr   	r   rU   rV   rZ   r[   r\   rW   rX   rY   )r]   responseexpectedr   r   missedrb   re   rt   rg   r`   s              r   %test_korean_keyword_found_in_responsez6TestKeywordMatch.test_korean_keyword_found_in_response   s    mL"/("Cv~vvv(~((((~(((~((((((((((((((((r   c                    d}d}t        ||      \  }}}d}||u }|st        j                  d|fd||f      dt        j                         v st        j
                  |      rt        j                  |      ndt        j                  |      dz  }dd	|iz  }	t        t        j                  |	            d
x}}t        |      }d}
||
kD  }|st        j                  d|fd||
f      dt        j                         v st        j
                  t              rt        j                  t              nddt        j                         v st        j
                  |      rt        j                  |      ndt        j                  |      t        j                  |
      dz  }dd|iz  }t        t        j                  |            d
x}x}}
y
)u;   한글 키워드가 응답에 없을 때 pass=False 반환.u:   죄송합니다. 이 요청은 처리할 수 없습니다.u<   헤드라인 10개와 설명 3개를 제공해야 합니다.Fr   r   r   r   rs   rL   Nr   >z/%(py3)s
{%(py3)s = %(py0)s(%(py1)s)
} > %(py6)srN   r   rI   rC   rK   rF   assert %(py8)spy8)
r   rU   rV   rZ   r[   r\   rW   rX   rY   rN   )r]   r   r   r   r   r   rb   re   rt   rg   ri   rf   rd   @py_format9s                 r   )test_korean_keyword_missing_from_responsez:TestKeywordMatch.test_korean_keyword_missing_from_response   s   OQ"/("Cvvvv6{Q{Q{Qss66{Qr   c                 v   d}d}t        ||      \  }}}d}||u }|st        j                  d|fd||f      dt        j                         v st        j
                  |      rt        j                  |      ndt        j                  |      dz  }dd	|iz  }	t        t        j                  |	            d
x}}y
)uh   부분 매칭이 지원되는지 확인 (예: '헤드라인 10' 중 '헤드라인'만 있어도 매칭).u'   다음은 헤드라인 목록입니다.u-   헤드라인 10개를 제공해야 합니다.Tr   r   r   r   rs   rL   Nr   )
r]   r   r   r   r   r   rb   re   rt   rg   s
             r   'test_partial_keyword_matching_supportedz8TestKeywordMatch.test_partial_keyword_matching_supported   sw    <B"/("Cv~vvvr   c                    d}d}t        ||      \  }}}d}||u }|st        j                  d|fd||f      dt        j                         v st        j
                  |      rt        j                  |      ndt        j                  |      dz  }dd	|iz  }	t        t        j                  |	            d
x}}d |D        }t        |      }
|
sddt        j                         v st        j
                  t              rt        j                  t              ndt        j                  |      t        j                  |
      dz  }t        t        j                  |            d
x}}
y
)u3   영문 키워드 대소문자 무시 매칭 확인.z*Here are the HEADLINES for google rsa ads.z%Provide headlines for Google RSA ads.Tr   r   r   r   rs   rL   Nc              3   B   K   | ]  }|j                         d k(    yw)	headlinesN)lower).0ks     r   	<genexpr>zJTestKeywordMatch.test_english_case_insensitive_matching.<locals>.<genexpr>   s     =1779+=s   z,assert %(py4)s
{%(py4)s = %(py0)s(%(py2)s)
}any)rI   rJ   rD   )
r   rU   rV   rZ   r[   r\   rW   rX   rY   r   )r]   r   r   r   r   r   rb   re   rt   rg   ra   rc   s               r   &test_english_case_insensitive_matchingz7TestKeywordMatch.test_english_case_insensitive_matching   s    ?:"/("Cv~vvv=W==s=========s===s==============r   c                    d}d}t        ||      \  }}}t        |t              }|sddt        j                         v st        j                  t              rt        j                  t              nddt        j                         v st        j                  |      rt        j                  |      nddt        j                         v st        j                  t              rt        j                  t              ndt        j                  |      dz  }t        t        j                  |            d}t        |      }d	}	||	kD  }
|
st        j                  d
|
fd||	f      dt        j                         v st        j                  t              rt        j                  t              nddt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |	      dz  }dd|iz  }t        t        j                  |            dx}x}
}	y)u?   매칭되지 않은 키워드 목록이 반환되는지 확인.u$   여기 헤드라인만 있습니다.u?   헤드라인과 설명과 CTA를 모두 제공해야 합니다.5assert %(py4)s
{%(py4)s = %(py0)s(%(py1)s, %(py2)s)
}rG   r   rH   rI   rC   rJ   rD   Nr   r   r   rN   r   r   r   )r   rG   rH   rZ   r[   rU   r\   rW   rX   rY   rN   rV   )r]   r   r   r   r   r   ra   rc   rb   ri   rf   rd   r   s                r   *test_keyword_match_returns_missed_keywordsz;TestKeywordMatch.test_keyword_match_returns_missed_keywords   s2   9T"/("C&$''''''''z'''z''''''&'''&''''''$'''$''''''''''6{Q{Q{Qss66{Qr   r   )	r   r   r   r   r   r   r   r   r   r   r   r   r   r      s    ))>r   r   c                   0    e Zd ZdZddZdeddfdZddZy)	TestCheckForbiddenu!   check_forbidden 함수 테스트.r   Nc                    d}d}t        ||      \  }}d}||u }|st        j                  d|fd||f      dt        j                         v st        j
                  |      rt        j                  |      ndt        j                  |      dz  }dd	|iz  }t        t        j                  |            d
x}}t        |      }d}	||	kD  }
|
st        j                  d|
fd||	f      dt        j                         v st        j
                  t              rt        j                  t              nddt        j                         v st        j
                  |      rt        j                  |      ndt        j                  |      t        j                  |	      dz  }dd|iz  }t        t        j                  |            d
x}x}
}	y
)ua   boundary에 '→ paid-ads' 라우팅 지시가 있을 때, 응답에서 범위 밖 처리 감지.ue   이 요청은 paid-ads 스킬을 사용하여 처리해야 합니다. paid-ads로 라우팅합니다.u1   → paid-ads: 캠페인 전략, 타겟팅, 예산Fr   r   r   r   rs   rL   Nr   r   r   rN   found_forbiddenr   r   r   
r   rU   rV   rZ   r[   r\   rW   rX   rY   rN   r]   r   boundaryr   r   rb   re   rt   rg   ri   rf   rd   r   s                r   +test_forbidden_routing_instruction_detectedz>TestCheckForbidden.test_forbidden_routing_instruction_detected   s   zF"1(H"E vvvv?#'a'#a''''#a''''''s'''s''''''?'''?'''#'''a'''''''r   r6   c                    d}t        ||      \  }}d}||u }|st        j                  d|fd||f      dt        j                         v st        j
                  |      rt        j                  |      ndt        j                  |      dz  }dd|iz  }t        t        j                  |            d	x}}g }||k(  }|st        j                  d
|fd||f      dt        j                         v st        j
                  |      rt        j                  |      ndt        j                  |      dz  }dd|iz  }t        t        j                  |            d	x}}y	)u>   정상 응답에 금지 내용이 없을 때 pass=True 반환.uR   헤드라인 10개를 작성해 드립니다. 1. 보험 약관 쉽게 이해하기Tr   r   r   r   rs   rL   Nr?   )z%(py0)s == %(py3)sr   )	r   rU   rV   rZ   r[   r\   rW   rX   rY   )	r]   r6   r   r   r   rb   re   rt   rg   s	            r   ,test_no_forbidden_content_in_normal_responsez?TestCheckForbidden.test_no_forbidden_content_in_normal_response   s    g"1(<P"Qv~vvv"$$"$$$$"$$$$$$$$$$$$"$$$$$$$r   c                    d}d}t        ||      \  }}d}||u }|st        j                  d|fd||f      dt        j                         v st        j
                  |      rt        j                  |      ndt        j                  |      dz  }dd	|iz  }t        t        j                  |            d
x}}t        |      }d}	||	kD  }
|
st        j                  d|
fd||	f      dt        j                         v st        j
                  t              rt        j                  t              nddt        j                         v st        j
                  |      rt        j                  |      ndt        j                  |      t        j                  |	      dz  }dd|iz  }t        t        j                  |            d
x}x}
}	y
)uK   보험업법 위반 단정적 표현('무조건', '100%') 감지 테스트.u:   이 보험은 무조건 보장되며 100% 지급됩니다.u6   무조건, 반드시, 100% 등 단정적 표현 금지Fr   r   r   r   rs   rL   Nr   r   r   rN   r   r   r   r   r   r   s                r   +test_absolute_guarantee_expression_detectedz>TestCheckForbidden.test_absolute_guarantee_expression_detected  s   OK"1(H"Evvvv?#'a'#a''''#a''''''s'''s''''''?'''?'''#'''a'''''''r   r   )r   r   r   r   r   rS   r   r   r   r   r   r   r      s$    +	(%QT %Y] %(r   r   c                   0    e Zd ZdZdeddfdZdeddfdZy)TestCheckRoutingu   check_routing 함수 테스트.r6   r   Nc                    d}t        ||      \  }}t        |t              }|sddt        j                         v st        j                  t              rt        j                  t              nddt        j                         v st        j                  |      rt        j                  |      nddt        j                         v st        j                  t              rt        j                  t              ndt        j                  |      dz  }t        t        j                  |            d}t        |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[   boundary에 '→ paid-ads' 지시가 있고 응답이 올바르게 위임할 때 pass=True.uf   캠페인 예산 전략은 paid-ads 스킬에서 담당합니다. 해당 스킬을 이용해주세요.r   rG   r   boolr   NreasonrS   )r	   rG   r   rZ   r[   rU   r\   rW   rX   rY   rS   )r]   r6   r   r   r   ra   rc   s          r   &test_routing_to_correct_skill_detectedz7TestCheckRouting.test_routing_to_correct_skill_detected  s(   {&x1EF&$''''''''z'''z''''''&'''&''''''$'''$''''''''''&#&&&&&&&&z&&&z&&&&&&&&&&&&&&&&&#&&&#&&&&&&&&&&r   c                    d}t        ||      \  }}d}||u }|st        j                  d|fd||f      dt        j                         v st        j
                  |      rt        j                  |      ndt        j                  |      dz  }dd|iz  }t        t        j                  |            d	x}}d
}||k7  }|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	)uK   스킬 범위 밖 요청에 대해 LLM이 적절히 거부하는지 확인.uX   예산 전략: CPC 300원, 일예산 50만원으로 설정하세요. 최적 타겟은...Fr   r   r   r   rs   rL   N )!=)z%(py0)s != %(py3)sr   )	r	   rU   rV   rZ   r[   r\   rW   rX   rY   )	r]   r6   r   r   r   rb   re   rt   rg   s	            r   &test_routing_refusal_when_out_of_scopez7TestCheckRouting.test_routing_refusal_when_out_of_scope  s     n&x1EFvvvvv|vvvr   )r   r   r   r   rS   r   r   r   r   r   r   r     s-    )'3 'SW '3 SW r   r   c                       e Zd ZdZdeeef   deddfdZdeeef   deddfdZdeeef   deddfd	Z	deeef   deddfd
Z
y)TestEvaluateResponseu*   evaluate_response 함수 통합 테스트.r   r8   r   Nc                 :   t        ||      }t        |t              }|sddt        j                         v st        j                  t              rt        j                  t              nddt        j                         v st        j                  |      rt        j                  |      nddt        j                         v st        j                  t              rt        j                  t              ndt        j                  |      dz  }t        t        j                  |            d}d}||v }|st        j                  d|fd	||f      t        j                  |      dt        j                         v st        j                  |      rt        j                  |      ndd
z  }dd|iz  }	t        t        j                  |	            dx}}d}||v }|st        j                  d|fd	||f      t        j                  |      dt        j                         v st        j                  |      rt        j                  |      ndd
z  }dd|iz  }	t        t        j                  |	            dx}}d}||v }|st        j                  d|fd	||f      t        j                  |      dt        j                         v st        j                  |      rt        j                  |      ndd
z  }dd|iz  }	t        t        j                  |	            dx}}y)uL   evaluate_response가 필수 키를 포함한 dict를 반환하는지 확인.r   rG   r_   r   r   Nr   rn   rp   rr   rs   rL   eval_iddetails)r
   rG   r   rZ   r[   rU   r\   rW   rX   rY   rV   )
r]   r   r8   r_   ra   rc   r`   rb   rt   rg   s
             r   6test_evaluate_response_returns_dict_with_required_keyszKTestEvaluateResponse.test_evaluate_response_returns_dict_with_required_keys%  s    ##46FG&$''''''''z'''z''''''&'''&''''''$'''$''''''''''!x6!!!!x6!!!x!!!!!!6!!!6!!!!!!!"yF""""yF"""y""""""F"""F""""""""yF""""yF"""y""""""F"""F"""""""r   c                    t        ||      }|d   }d}||u }|slt        j                  d|fd||f      t        j                  |      t        j                  |      dz  }dd|iz  }t	        t        j
                  |            dx}x}}y)	uE   헤드라인을 포함한 정상 응답에 대해 passed=True 반환.r   Tr   z%(py1)s is %(py4)srB   rE   rF   Nr
   rU   rV   rW   rX   rY   	r]   r   r8   r_   r`   ra   rb   rc   rd   s	            r    test_evaluate_response_pass_casez5TestEvaluateResponse.test_evaluate_response_pass_case2  sd     ##46FGh'4'4''''4''''''4'''''''r   r:   c                    t        ||      }|d   }d}||u }|slt        j                  d|fd||f      t        j                  |      t        j                  |      dz  }dd|iz  }t	        t        j
                  |            dx}x}}y)	u9   키워드가 없는 응답에 대해 passed=False 반환.r   Fr   r   rB   rE   rF   Nr   )	r]   r   r:   r_   r`   ra   rb   rc   rd   s	            r    test_evaluate_response_fail_casez5TestEvaluateResponse.test_evaluate_response_fail_case<  sd     ##46FGh(5(5((((5((((((5(((((((r   c                 "   t        ||      }|d   }|d   }||k(  }|slt        j                  d|fd||f      t        j                  |      t        j                  |      dz  }dd|iz  }t	        t        j
                  |            dx}x}}y)	u,   결과에 eval id가 포함되는지 확인.r   r   r?   rA   rB   rE   rF   Nr   r   s	            r   'test_evaluate_response_includes_eval_idz<TestEvaluateResponse.test_evaluate_response_includes_eval_idF  sm     ##46FGi :$4T$:: $::::: $:::: :::$::::::::r   )r   r   r   r   r   rS   r   r   r   r   r   r   r   r   r   r   "  s    4#sCx.# # 
	#(sCx.( ( 
	()sCx.) ) 
	);sCx.; ; 
	;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)	TestCLIParsingu   CLI 인수 파싱 테스트.Nc                    ddl }ddlm}  |       }|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  }d
d|iz  }	t        t        j                  |	            dx}x}}y)uB   --skill ad-creative 인수가 올바르게 파싱되는지 확인.r   Ncreate_argument_parser--skillr   r?   )z-%(py2)s
{%(py2)s = %(py0)s.skill
} == %(py5)sargsrI   rJ   rL   assert %(py7)srP   )argparse	run_evalsr   
parse_argsskillrU   rV   rZ   r[   r\   rW   rX   rY   )
r]   r   r   parserr   re   rf   ra   rg   rj   s
             r   test_parse_skill_argumentz(TestCLIParsing.test_parse_skill_argumentY  s     	5')  )]!;<zz*]*z]****z]******t***t***z***]*******r   c                    ddl m}  |       }|j                  dg      }|j                  }d}||u }|st	        j
                  d|fd||f      dt        j                         v st	        j                  |      rt	        j                  |      ndt	        j                  |      t	        j                  |      dz  }d	d
|iz  }t        t	        j                  |            dx}x}}y)u7   --all 플래그가 올바르게 파싱되는지 확인.r   r   z--allTr   )z+%(py2)s
{%(py2)s = %(py0)s.all
} is %(py5)sr   r   r   rP   N)r   r   r   allrU   rV   rZ   r[   r\   rW   rX   rY   	r]   r   r   r   re   rf   ra   rg   rj   s	            r   test_parse_all_flagz"TestCLIParsing.test_parse_all_flage  s    4')  '+xx4x4x4ttx4r   c                    ddl m}  |       }|j                  g d      }|j                  }d}||u }|st	        j
                  d|fd||f      dt        j                         v st	        j                  |      rt	        j                  |      ndt	        j                  |      t	        j                  |      dz  }d	d
|iz  }t        t	        j                  |            dx}x}}y)u;   --verbose 플래그가 올바르게 파싱되는지 확인.r   r   )r   r   z	--verboseTr   )z/%(py2)s
{%(py2)s = %(py0)s.verbose
} is %(py5)sr   r   r   rP   N)r   r   r   verboserU   rV   rZ   r[   r\   rW   rX   rY   r   s	            r   test_parse_verbose_flagz&TestCLIParsing.test_parse_verbose_flagn      4')  !HI||#t#|t####|t######t###t###|###t#######r   c                    ddl m}  |       }|j                  g d      }|j                  }d}||u }|st	        j
                  d|fd||f      dt        j                         v st	        j                  |      rt	        j                  |      ndt	        j                  |      t	        j                  |      dz  }d	d
|iz  }t        t	        j                  |            dx}x}}y)u;   --dry-run 플래그가 올바르게 파싱되는지 확인.r   r   )r   r   z	--dry-runTr   )z/%(py2)s
{%(py2)s = %(py0)s.dry_run
} is %(py5)sr   r   r   rP   N)r   r   r   dry_runrU   rV   rZ   r[   r\   rW   rX   rY   r   s	            r   test_parse_dry_run_flagz&TestCLIParsing.test_parse_dry_run_flagw  r   r   c                     ddl m} t        j                  t        t
        f      5   |d       ddd       y# 1 sw Y   yxY w)uI   존재하지 않는 스킬명에 대해 에러가 발생하는지 확인.r   )validate_skill_nameznonexistent-skill-xyz-123N)r   r   rw   rx   rz   
SystemExit)r]   r   s     r   $test_invalid_skill_name_raises_errorz3TestCLIParsing.test_invalid_skill_name_raises_error  s4    1]]J
34 	= ;<	= 	= 	=s	   	8Ar   )	r   r   r   r   r   r   r   r   r   r   r   r   r   r   V  s    &
+ $$=r   r   c                       e Zd ZdZej
                  deeee	f      fd       Z
deeee	f      deddfdZdeeee	f      deddfdZdeeee	f      deddfd	Zy)
TestResultSummaryu-   결과 요약 및 보고서 생성 테스트.r   c           	      N    dddi ddddi dddddd	gidd
ddi ddddddgidgS )u,   pass/fail이 섞인 결과 목록 픽스처.r   r   T)r   r    r   r   r      Fr   r      copywriting   CTAr   r   s    r   mixed_resultszTestResultSummary.mixed_results  sa     $SUV$SUV%U]`n_oTpq$SUV%U]`e_fTgh
 	
r   r   r#   Nc           	         t        |dz        }t        ||      }t        |t               }|sddt        j                         v st        j                  t              rt        j                  t              nddt        j                         v st        j                  |      rt        j                  |      nddt        j                         v st        j                  t               rt        j                  t               ndt        j                  |      dz  }t        t        j                  |            d}t        j                  |      }d}||v }	|	st        j                  d	|	fd
||f      t        j                  |      dt        j                         v st        j                  |      rt        j                  |      nddz  }
dd|
iz  }t        t        j                  |            dx}}	|d   d   d   }d}	d}|	|z  }||z
  }t        |      }d}||k  }|st        j                  d|fd||f      dt        j                         v st        j                  t              rt        j                  t              nddt        j                         v st        j                  |      rt        j                  |      ndt        j                  |	      t        j                  |      t        j                  |      t        j                  |      dz  }dd|iz  }t        t        j                  |            dx}	x}x}x}x}x}}y)u;   스킬별 pass rate가 올바르게 계산되는지 확인.report.jsonr   rG   reportrS   r   Nskill_resultsrn   rp   report_datarr   rs   rL   r   	pass_rater   r   {Gz?<)zH%(py9)s
{%(py9)s = %(py0)s((%(py1)s - (%(py3)s / %(py5)s)))
} < %(py12)sabsad_creative_rate)rI   rC   rK   rL   rR   py12zassert %(py14)spy14)rS   r   rG   rZ   r[   rU   r\   rW   rX   rY   r0   loadsrV   r  )r]   r   r#   report_pathr   ra   rc   r  r`   rb   rt   rg   r  rf   rh   @py_assert7@py_assert8@py_assert11@py_assert10@py_format13@py_format15s                        r    test_skill_pass_rate_calculationz2TestResultSummary.test_skill_pass_rate_calculation  s   (]23 <&#&&&&&&&&z&&&z&&&&&&&&&&&&&&&&&#&&&#&&&&&&&&&&jj(-+----+---------+---+-------&7F{S&'3!3a!e3#e+3s+,3t3,t3333,t333333s333s333333#333#333a333!333,333t33333333r   c           	         t        |dz        }t        ||      }t        j                  |      }d}||v }|st	        j
                  d|fd||f      t	        j                  |      dt        j                         v st	        j                  |      rt	        j                  |      nddz  }dd|iz  }	t        t	        j                  |	            d	x}}|d   }
d
}d}||z  }|
|z
  }t        |      }d}||k  }|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                  |      t	        j                  |      t	        j                  |      dz  }dd|iz  }t        t	        j                  |            d	x}
x}x}x}x}x}x}}y	)u8   전체 pass rate가 올바르게 계산되는지 확인.r   total_pass_ratern   rp   r  rr   rs   rL   Nr   r   r  r  )zJ%(py10)s
{%(py10)s = %(py0)s((%(py2)s - (%(py4)s / %(py6)s)))
} < %(py13)sr  )rI   rJ   rD   rF   py10py13zassert %(py15)spy15)rS   r   r0   r  rU   rV   rW   rZ   r[   r\   rX   rY   r  )r]   r   r#   r  r   r  r`   rb   rt   rg   re   ra   ri   r  r  @py_assert9@py_assert12r  @py_format14@py_format16s                       r    test_total_pass_rate_calculationz2TestResultSummary.test_total_pass_rate_calculation  sG   (]23 <jj( / K//// K/// //////K///K///////01AAAAAEA1E9As9:ATA:TAAAA:TAAAAAAsAAAsAAA1AAAAAAAAAA:AAATAAAAAAAAr   c                 ,   t        |dz        }t        ||      }t        j                  |      }d}||v }|st	        j
                  d|fd||f      t	        j                  |      dt        j                         v st	        j                  |      rt	        j                  |      nddz  }dd|iz  }	t        t	        j                  |	            d	x}}|d   }
t        |
      }d
}||k(  }|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  }dd|iz  }t        t	        j                  |            d	x}
x}x}}|d   D ]M  }d}||v }|st	        j
                  d|fd||f      t	        j                  |      dt        j                         v st	        j                  |      rt	        j                  |      nddz  }dd|iz  }	t        t	        j                  |	            d	x}}d}||v }|st	        j
                  d|fd||f      t	        j                  |      dt        j                         v st	        j                  |      rt	        j                  |      nddz  }dd|iz  }	t        t	        j                  |	            d	x}}P y	)u:   FAIL 케이스에 상세 정보가 포함되는지 확인.r   failed_casesrn   rp   r  rr   rs   rL   Nr   r?   rM   rN   rO   rQ   rR   r   	fail_caser   )rS   r   r0   r  rU   rV   rW   rZ   r[   r\   rX   rY   rN   )r]   r   r#   r  r   r  r`   rb   rt   rg   re   ra   rh   ri   rj   rk   r   s                    r   test_fail_cases_include_detailsz1TestResultSummary.test_fail_cases_include_details  s   (]23 <jj(,~,,,,~,,,~,,,,,,,,,,,,,,,,~.4s./414/14444/1444444s444s444.444/44414444444$^4 	*I)9	))))9	)))9))))))	)))	))))))))9	))))9	)))9))))))	)))	)))))))	*r   )r   r   r   r   rw   fixturerH   r   rS   r   r   r   r  r  r!  r   r   r   r   r     s    7^^
tDcN3 
 
	4d4S>>R 	4^b 	4gk 	4Bd4S>>R B^b Bgk B
*T$sCx.=Q 
*]a 
*fj 
*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y)
TestDryRunu   dry-run 모드 테스트.r4   r   Nc                 .   ddl m} t        |j                  j                  j                        }t	               }t        d|      5  t        d|      5   |dd       ddd       ddd       |j                          y# 1 sw Y   "xY w# 1 sw Y   &xY w)	u8   dry-run 모드에서 LLM API 호출이 없는지 확인.r   run_evals_for_skillr>   zrun_evals.call_llmr   Tr   N)r   r'  rS   rT   r   r   assert_not_called)r]   r4   r'  r^   mock_llm_calls        r   "test_dry_run_does_not_call_llm_apiz-TestDryRun.test_dry_run_does_not_call_llm_api  s    
 	2..55<<=	!/; 	A+]; A#M4@A	A 	'')A A	A 	As$   BA?B?B	BBc                 L   ddl m} t        |j                  j                  j                        }t	        d|      5   |dd      }ddd       d}|u}|st        j                  d|fd	||f      d
t        j                         v st        j                  |      rt        j                  |      nd
t        j                  |      dz  }dd|iz  }t        t        j                  |            dx}}d}	|	|v }|st        j                  d|fd|	|f      t        j                  |	      d
t        j                         v st        j                  |      rt        j                  |      nd
dz  }dd|iz  }t        t        j                  |            dx}	}|d   }	d}
|	|
u }|slt        j                  d|fd|	|
f      t        j                  |	      t        j                  |
      dz  }dd|iz  }t        t        j                  |            dx}	x}}
y# 1 sw Y   xY w)uC   dry-run 모드에서 evals 구조 검증이 수행되는지 확인.r   r&  r>   r   Tr(  N)is not)z%(py0)s is not %(py3)sr_   r   rs   rL   r   rn   rp   rr   r   r   rB   rE   rF   )r   r'  rS   rT   r   rU   rV   rZ   r[   r\   rW   rX   rY   )r]   r4   r'  r^   r_   rb   re   rt   rg   r`   ra   rc   rd   s                r   &test_dry_run_validates_evals_structurez1TestDryRun.test_dry_run_validates_evals_structure  sY   
 	2..55<<=	/; 	F(EF	F "!vT!!!!vT!!!!!!v!!!v!!!T!!!!!!!"yF""""yF"""y""""""F"""F"""""""i (D( D(((( D((( (((D(((((((	F 	Fs   HH#c                    ddl m} t        |j                  j                  j                        }t	        d|      5   |dd      }ddd       d}|v }|st        j                  d	|fd
||f      t        j                  |      dt        j                         v st        j                  |      rt        j                  |      nddz  }dd|iz  }t        t        j                  |            dx}}|d   }d}	||	k(  }|slt        j                  d|fd||	f      t        j                  |      t        j                  |	      dz  }
dd|
iz  }t        t        j                  |            dx}x}}	y# 1 sw Y   0xY w)uB   dry-run 모드에서 eval 케이스 수를 반환하는지 확인.r   r&  r>   r   Tr(  N
eval_countrn   rp   r_   rr   rs   rL   r   r?   rA   rB   rE   rF   )r   r'  rS   rT   r   rU   rV   rW   rZ   r[   r\   rX   rY   )r]   r4   r'  r^   r_   r`   rb   rt   rg   ra   rc   rd   s               r   test_dry_run_returns_eval_countz*TestDryRun.test_dry_run_returns_eval_count  s    
 	2..55<<=	/; 	F(EF	F %|v%%%%|v%%%|%%%%%%v%%%v%%%%%%%l#(q(#q((((#q(((#(((q(((((((		F 	Fs   E55E?)r   r   r   r   r   r+  r.  r1  r   r   r   r$  r$    sJ    #** 
* )) 
)")) 
)r   r$  c                   (    e Zd ZdZddZddZddZy)TestGetSkillListu    get_skill_list 함수 테스트.Nc                 X   t               }t        |t              }|sddt        j                         v st        j                  t              rt        j                  t              nddt        j                         v st        j                  |      rt        j                  |      nddt        j                         v st        j                  t              rt        j                  t              ndt        j                  |      dz  }t        t        j                  |            d}y)u6   get_skill_list가 리스트를 반환하는지 확인.r   rG   r_   rH   r   N)
r   rG   rH   rZ   r[   rU   r\   rW   rX   rY   )r]   r_   ra   rc   s       r    test_get_skill_list_returns_listz1TestGetSkillList.test_get_skill_list_returns_list  s    !&$''''''''z'''z''''''&'''&''''''$'''$''''''''''r   c                 b   t               }d}||v }|st        j                  d|fd||f      t        j                  |      dt	        j
                         v st        j                  |      rt        j                  |      nddz  }dd|iz  }t        t        j                  |            dx}}y)	u7   ad-creative 스킬이 목록에 포함되는지 확인.r   rn   rp   r_   rr   rs   rL   N)	r   rU   rV   rW   rZ   r[   r\   rX   rY   )r]   r_   r`   rb   rt   rg   s         r   (test_get_skill_list_includes_ad_creativez9TestGetSkillList.test_get_skill_list_includes_ad_creative  sc    !&}&&&&}&&&}&&&&&&&&&&&&&&&&r   c                 8   t               }t        |      }d}||kD  }|st        j                  d|fd||f      dt	        j
                         v st        j                  t              rt        j                  t              nddt	        j
                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      dz  }dd|iz  }t        t        j                  |            d	x}x}}y	)
u/   스킬 목록이 비어있지 않은지 확인.r   r   r   rN   r_   r   r   r   N)
r   rN   rU   rV   rZ   r[   r\   rW   rX   rY   )r]   r_   rb   ri   rf   rd   r   s          r   )test_get_skill_list_returns_nonempty_listz:TestGetSkillList.test_get_skill_list_returns_nonempty_list  s    !6{Q{Q{Qss66{Qr   r   )r   r   r   r   r5  r7  r9  r   r   r   r3  r3    s    *(
'
r   r3  )-r   builtinsrZ   _pytest.assertion.rewrite	assertionrewriterU   r0   ostempfilepathlibr   typingr   unittest.mockr   r   r   rw   r   r   r	   r
   r   r   r   r   r"  r   rS   r   r"   r4   r6   r8   r:   r<   r   r   r   r   r   r   r$  r3  r   r   r   <module>rC     s  
   	    5 5    $sCx.  $ S#X 4S>  " tCH~  RV   c   3   g3 g g@0 @0P2 2j( (B ,,; ,;h/= /=n,* ,*h1) 1)r r   