
    (<i                        U d Z ddlZddlZddlZddlmZmZ ddlmZ ddl	m
Z
 ddlmZmZ ddlZ ee      j                   j                   Zedz  Zej&                  j)                  de      Zeej,                  J ej&                  j/                  e      Zeej2                  d<   ej,                  j5                  e       ed	z  Zej&                  j)                  d
e      Zeej,                  J ej&                  j/                  e      Zeej2                  d
<   ej,                  j5                  e       dedeeef   fdZ dedeee!e   f   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+ G d# d$      Z, G d% d&      Z- G d' d(      Z. G d) d*      Z/ G d+ d,      Z0 G d- d.      Z1 G d/ d0      Z2ed1z  Z3d2edefd3Z4d4d5d6d7gd8d7gd9d:Z5eee
f   e6d;<   d<d=d6d7gd8d7gd9d:Z7eee
f   e6d><   d?d7d8gd7d6gd9d@dAZ8eee
f   e6dB<   dCd8d7gd6d7gd9dDdAZ9eee
f   e6dE<   dFg dGZ:eee
f   e6dH<   dIdJdKgdGZ;eee
f   e6dL<   dMe7dNdOid?e8dPdQdRgdSZ<eee
f   e6dT<    G dU dV      Z= G dW dX      Z> G dY dZ      Z? G d[ d\      Z@ G d] d^      ZA G d_ d`      ZB G da db      ZCy)cu  Tests for output-review.py and output_review_helpers.py — TDD RED phase.

헬퍼 모듈(output_review_helpers)과 메인 스크립트(output-review)에 대한 테스트.
아직 구현이 없으므로 ImportError 또는 AttributeError로 실패하는 것이 정상(RED 단계).
    N)datetimetimezone)Path)Any)	MagicMockpatchzoutput_review_helpers.pyoutput_review_helperszoutput-review.pyoutput_reviewtmp_pathreturnc                     | dz  dz  dz  }|j                  d       | dz  dz  dz  }|j                  d       | dz  dz  dz  }|j                          | dz  d	z  }|j                  d       ||||d
S )u(   테스트용 디렉토리 구조 생성.memoryskill-learning	championsT)parentszchampions-archivelearnings.jsonlskillsshared)champions_dirarchive_dirlearnings_pathskills_shared)mkdirtouch)r   r   r   r   r   s        Q/home/jay/workspace/.worktrees/task-2057-dev2/scripts/tests/test_output_review.py_make_tmp_dirsr   .   s    x'*::[HM%X%(88;NNKd#(+;;>OONx'(2M%&"(&	     r   datac                 \    | dz  }|j                  t        j                  |      d       |S )u   eval-axes.json 파일 생성.zeval-axes.jsonutf-8encoding)
write_textjsondumps)r   r   paths      r   _make_eval_axes_jsonr'   @   s+    ++DOODJJt$wO7Kr   c                   <    e Zd ZddZddZddZddZddZddZy)	TestBuildChampionDataNc                 n    t         j                  dddddg      }g d}|D ]  }||v rJ d| d	        y
)uB   champion 데이터에 필수 필드가 모두 존재해야 한다.satori-cardnewsbusinesszsample output text
   훅 강도   시각 밸런스
skill_name
skill_typechampion_outputeval_axes_used)r0   r1   r2   r3   
created_at	last_usedstatusconsecutive_defensesconsecutive_lossesreinit_count_this_monthmonth_reset_dateinit_methodbenchmark_sourceu   필드 'u   ' 누락Norhbuild_champion_data)selfr   required_fieldsfields       r   (test_build_champion_data_required_fieldsz>TestBuildChampionData.test_build_champion_data_required_fieldsM   s[    &&(!0(*<=	 ' 

 % 	=ED=<HUG8"<<=	=r   c                 Z   t         j                  ddddg      }t        |d   t              sJ t        |d   t              sJ t        |d   t              sJ t        |d	   t              sJ t        |d
   t              sJ t        |d   t              sJ t        |d   t              sJ t        |d   t
              sJ t        |d   t
              sJ t        |d   t
              sJ t        |d   t              sJ t        |d   t              sJ t        |d   t              sJ y)u9   각 필드의 타입이 스키마와 일치해야 한다.r+   r,   outputr-   r/   r0   r1   r2   r3   r4   r5   r6   r7   r8   r9   r:   r;   r<   N)r>   r?   
isinstancestrlistintr@   r   s     r   $test_build_champion_data_field_typesz:TestBuildChampionData.test_build_champion_data_field_typesg   s>   &&(!$(>	 ' 
 $|,c222$|,c222$013777$/0$777$|,c222${+S111$x.#...$56<<<$34c:::$893???$12C888$}-s333$12C888r   c                     t         j                  dddg       }|d   dk(  sJ |d   dk(  sJ |d	   dk(  sJ |d
   dk(  sJ |d   dk(  sJ |d   dk(  sJ y)u8   초기값이 스키마 기본값과 일치해야 한다.
test-skillcreativerE   r/   r6   activer7   r   r8   r9   r;   full_benchmarkr<   online_expertNr=   rJ   s     r   'test_build_champion_data_default_valuesz=TestBuildChampionData.test_build_champion_data_default_values}   s    &&#!$	 ' 
 H~)))*+q000()Q...-.!333M"&6666&'?:::r   c                 d    t         j                  ddddgdd      }|d   dk(  sJ |d	   dk(  sJ y
)uI   init_method 및 benchmark_source 커스텀 값이 적용되어야 한다.rM   
analyticalrE   accuracymanualinternal_team)r0   r1   r2   r3   r;   r<   r;   r<   Nr=   rJ   s     r   +test_build_champion_data_custom_init_methodzATestBuildChampionData.test_build_champion_data_custom_init_method   sT    &&##$&< , ' 
 M"h...&'?:::r   c                 J    t         j                  dddg       }|d   dk(  sJ y)u/   skill_name이 정확히 보존되어야 한다.zmy-special-skillr,   rE   r/   r0   Nr=   rJ   s     r   -test_build_champion_data_skill_name_preservedzCTestBuildChampionData.test_build_champion_data_skill_name_preserved   s9    &&)!$	 ' 
 L!%7777r   c                 R    g d}t         j                  ddd|      }|d   |k(  sJ y)u=   eval_axes_used 리스트가 그대로 저장되어야 한다.r-   r.      정보 밀도rM   r,   rE   r/   r3   Nr=   )r@   axesr   s      r   ,test_build_champion_data_eval_axes_preservedzBTestBuildChampionData.test_build_champion_data_eval_axes_preserved   s>    B&&#!$	 ' 
 $%---r   r   N)	__name__
__module____qualname__rC   rK   rR   rX   rZ   r_    r   r   r)   r)   L   s     =49,;;8	.r   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dedej
                  ddfdZy)TestSaveAndLoadChampionr   monkeypatchr   Nc                 0   t        |       |j                  dt        |             t        j	                  ddddg      }t        j                  d|       t        j                  d      }|J |d   |d   k(  sJ |d	   |d	   k(  sJ |d
   |d
   k(  sJ y)uF   저장 후 로드하면 동일한 데이터가 반환되어야 한다.WORKSPACE_ROOTr+   r,   ztest outputr-   r/   Nr0   r2   r3   )r   setenvrG   r>   r?   save_championload_champion)r@   r   rg   champion_dataloadeds        r   %test_save_and_load_champion_roundtripz=TestSaveAndLoadChampion.test_save_and_load_champion_roundtrip   s    x +S];//(!)(>	 0 
 	+];""#45!!!l#}\'BBBB'(M:K,LLLL&'=9I+JJJJr   c                    t        |       |j                  dt        |             t        j	                  dddg       }t        j                  d|      }t        |t              sJ |j                         sJ |j                  dk(  sJ y)u@   save_champion은 저장된 파일 경로를 반환해야 한다.ri   rM   r,   rE   r/   ztest-skill.jsonN)
r   rj   rG   r>   r?   rk   rF   r   existsname)r@   r   rg   r   result_paths        r   test_save_champion_returns_pathz7TestSaveAndLoadChampion.test_save_champion_returns_path   s    x +S];&&#!$	 ' 
 ''d;+t,,,!!####4444r   c                    t        |       |j                  dt        |             t        j	                  ddddg      }t        j                  d|      }|j                  d      }t        j                  |      }|d	   dk(  sJ y
)u3   저장된 파일이 유효한 JSON이어야 한다.ri   z	json-testr,   rE   axis1r/   r    r!   r0   N)	r   rj   rG   r>   r?   rk   	read_textr$   loads)r@   r   rg   r   
saved_pathrawparseds          r   %test_save_champion_creates_valid_jsonz=TestSaveAndLoadChampion.test_save_champion_creates_valid_json   s    x +S];&&"!$#9	 ' 
 &&{D9
""G"4Cl#{222r   )	ra   rb   rc   r   pytestMonkeyPatchro   rt   r|   rd   r   r   rf   rf      sk    Kd KQWQcQc Khl K&5 56K]K] 5bf 5"3d 3QWQcQc 3hl 3r   rf   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)TestLoadChampionNotExistsr   rg   r   Nc                     t        |       |j                  dt        |             t        j	                  d      }|J y)uB   존재하지 않는 스킬 로드 시 None을 반환해야 한다.ri   nonexistent-skillNr   rj   rG   r>   rl   r@   r   rg   results       r   ,test_load_champion_returns_none_when_missingzFTestLoadChampionNotExists.test_load_champion_returns_none_when_missing   s:    x +S];""#67~~r   c                     t        |       |j                  dt        |             t        j	                  d      }|J y)uN   존재하지 않는 스킬 로드 시 예외가 발생하지 않아야 한다.ri   zmissing-skill-xyzNr   r   s       r   !test_load_champion_does_not_raisez;TestLoadChampionNotExists.test_load_champion_does_not_raise   s<    x +S]; ""#67~~r   )ra   rb   rc   r   r}   r~   r   r   rd   r   r   r   r      sE    T X^XjXj os $ VM_M_ dh r   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dedej
                  ddfdZy)TestArchiveChampionr   rg   r   Nc                    t        |      }|j                  dt        |             t        j	                  ddddg      }t        j                  d|       t        j                  d      }|J |j                         sJ |d   |j                  v st        |d         t        |      v sJ |j                  d	k(  sJ y)
u[   기존 챔피언을 아카이브하면 타임스탬프 파일명으로 이동해야 한다.ri   zarchive-testr,   z
old outputrv   r/   Nr   z.json)
r   rj   rG   r>   r?   rk   archive_championrq   r   suffix)r@   r   rg   dirsrm   archived_paths         r    test_archive_champion_moves_filez4TestArchiveChampion.test_archive_champion_moves_file  s    h'+S]; //%!(#9	 0 
 	.-8,,^<(((##%%%M"m&;&;;s4CV?W[^_l[m?mmm##w...r   c                 0   t        |       |j                  dt        |             t        j	                  dddg       }t        j                  d|       t        j                  d      }|J |j                  }t        d |D              s
J d|        y)	uD   아카이브 파일명에 타임스탬프가 포함되어야 한다.ri   zts-testr,   rE   r/   Nc              3   <   K   | ]  }|j                           y wN)isdigit).0chs     r   	<genexpr>zSTestArchiveChampion.test_archive_champion_filename_has_timestamp.<locals>.<genexpr>0  s     /B2::</s   u   타임스탬프 없음: )	r   rj   rG   r>   r?   rk   r   stemany)r@   r   rg   r   r   r   s         r   ,test_archive_champion_filename_has_timestampz@TestArchiveChampion.test_archive_champion_filename_has_timestamp  s    x +S];&& !$	 ' 
 	)T*,,Y7(((!!/$//R3KD61RR/r   c                 0   t        |      }|j                  dt        |             t        j	                  dddg       }t        j                  d|       |d   dz  }|j                         sJ t        j                  d       |j                         rJ y)	uP   아카이브 후 원본 champions/{skill}.json이 존재하지 않아야 한다.ri   zremove-testr,   rE   r/   r   zremove-test.jsonN)r   rj   rG   r>   r?   rk   rq   r   )r@   r   rg   r   r   originals         r   &test_archive_champion_original_removedz:TestArchiveChampion.test_archive_champion_original_removed2  s    h'+S];&&$!$	 ' 
 	-.(+==   ]+??$$$$r   )	ra   rb   rc   r   r}   r~   r   r   r   rd   r   r   r   r     sk    / /FL^L^ /cg /.ST SX^XjXj Sos S&%t %RXRdRd %im %r   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)TestArchiveChampionNoExistingr   rg   r   Nc                     t        |       |j                  dt        |             t        j	                  d      }|J y)uC   아카이브할 챔피언이 없으면 None을 반환해야 한다.ri   zdoes-not-existNr   rj   rG   r>   r   r   s       r   .test_archive_nonexistent_champion_returns_nonezLTestArchiveChampionNoExisting.test_archive_nonexistent_champion_returns_noneK  s:    x +S];%%&67~~r   c                     t        |       |j                  dt        |             t        j	                  d      }|J y)uO   아카이브할 챔피언이 없어도 예외가 발생하지 않아야 한다.ri   zghost-skillNr   r   s       r   'test_archive_nonexistent_does_not_raisezETestArchiveChampionNoExisting.test_archive_nonexistent_does_not_raiseS  s;    x +S]; %%m4~~r   )ra   rb   rc   r   r}   r~   r   r   rd   r   r   r   r   J  sF    t Z`ZlZl qu  SYSeSe jn r   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dedej
                  ddfdZdedej
                  ddfdZ	y)	TestAppendLearningr   rg   r   Nc                    t        |       |j                  dt        |             |dz  dz  dz  }t        |j	                  d      j                               }ddd	t        j                  t        j                        j                         d
}t        j                  |       t        |j	                  d      j                               }||dz   k(  sJ y)uL   append_learning 호출 시 learnings.jsonl 줄 수가 1 증가해야 한다.ri   r   r   r   r    r!   rM   Au   더 나은 훅)r0   winnerreason	timestamp   N)r   rj   rG   lenrw   
splitlinesr   nowr   utc	isoformatr>   append_learning)r@   r   rg   r   initial_linesentry	new_liness          r   )test_append_learning_increases_line_countz<TestAppendLearning.test_append_learning_increases_line_countc  s    x +S];!H,/??BSSN44g4FQQST '&!hll3==?	
 	E"00'0BMMOP	MA----r   c                 <   t        |       |j                  dt        |             dddd}t        j	                  |       |dz  dz  dz  }|j                  d	
      j                         j                         d   }t        j                  |      }|d   dk(  sJ y)u0   append된 줄이 유효한 JSON이어야 한다.ri   z
json-skilldefenseg333333?)r0   eventscorer   r   r   r    r!   r0   N)
r   rj   rG   r>   r   rw   stripr   r$   rx   )r@   r   rg   r   r   	last_liner{   s          r   $test_append_learning_valid_json_linez7TestAppendLearning.test_append_learning_valid_json_linev  s    x +S];+i$OE"!H,/??BSS",,g,>DDFQQSTVW	I&l#|333r   c                 b   t        |       |j                  dt        |             t        d      D ]  }t        j                  |d| d        |dz  dz  dz  }|j                  d	      j                         D cg c]  }|j                         s| }}t        |      dk(  sJ y
c c}w )u;   여러 번 append 시 각각 줄로 저장되어야 한다.ri      zskill-)indexr0   r   r   r   r    r!   N)
r   rj   rG   ranger>   r   rw   r   r   r   )r@   r   rg   ir   lliness          r   %test_append_learning_multiple_entriesz8TestAppendLearning.test_append_learning_multiple_entries  s    x +S];q 	JA!VA3< HI	J "H,/??BSS*44g4FQQSaqWXW^W^W`aa5zQ bs   ?B,B,c                    t        |       |j                  dt        |             |dz  dz  dz  }|j                  t	        j
                  ddi      dz   d	       t        j                  d
di       |j                  d	      j                         D cg c]  }|j                         s| }}t        |      dk(  sJ t	        j                  |d         d   dk(  sJ yc c}w )uP   append_learning은 기존 내용을 덮어쓰지 않아야 한다 (append-only).ri   r   r   r   existingr   
r    r!   new   r   N)r   rj   rG   r#   r$   r%   r>   r   rw   r   r   r   rx   )r@   r   rg   r   r   r   s         r   'test_append_learning_does_not_overwritez:TestAppendLearning.test_append_learning_does_not_overwrite  s    x +S];!H,/??BSS!!$**j'-B"Cd"JU\!]UG,-*44g4FQQSaqWXW^W^W`aa5zQzz%(#J/7::: bs   C#,C#)
ra   rb   rc   r   r}   r~   r   r   r   r   rd   r   r   r   r   b  s    .$ .U[UgUg .lp .&4T 4PVPbPb 4gk 4
d 
QWQcQc 
hl 
; ;SYSeSe ;jn ;r   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)TestLoadEvalAxesr   rg   r   Nc                     t        |      }|j                  dt        |             t        |d   g ddgd       t        j                  d      }t        |t              sJ |g dk(  sJ y)uR   eval-axes.json에서 해당 스킬의 평가 축 리스트를 반환해야 한다.ri   r   r\   rU   )r+   zother-skillr+   N)r   rj   rG   r'   r>   load_eval_axesrF   rH   )r@   r   rg   r   r^   s        r    test_load_eval_axes_returns_listz1TestLoadEvalAxes.test_load_eval_axes_returns_list  sp    h'+S];!#V *|	
 !!"34$%%%JJJJr   c                     t        |      }|j                  dt        |             t        |d   ddgdgd       t        j                  d      }t        j                  d      }|ddgk(  sJ |dgk(  sJ y	)
u6   다른 스킬의 축과 혼동되지 않아야 한다.ri   r   zaxis-a1zaxis-a2zaxis-b1)skill-askill-br   r   Nr   rj   rG   r'   r>   r   )r@   r   rg   r   axes_aaxes_bs         r   !test_load_eval_axes_correct_skillz2TestLoadEvalAxes.test_load_eval_axes_correct_skill  s    h'+S];!%y1%;	
 ##I.##I.)Y////)$$$r   )ra   rb   rc   r   r}   r~   r   r   rd   r   r   r   r     sH    K KFL^L^ Kcg K"%$ %VM_M_ %dh %r   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)TestLoadEvalAxesMissingSkillr   rg   r   Nc                     t        |      }|j                  dt        |             t        |d   ddgi       t        j                  d      }|g k(  sJ y)uH   eval-axes.json에 없는 스킬은 빈 리스트를 반환해야 한다.ri   r   zexisting-skillrv   r   Nr   )r@   r   rg   r   r   s        r   %test_missing_skill_returns_empty_listzBTestLoadEvalAxesMissingSkill.test_missing_skill_returns_empty_list  sZ    h'+S];!y)	

 ##$78||r   c                     t        |       |j                  dt        |             t        j	                  d      }|g k(  sJ y)uO   eval-axes.json 파일 자체가 없어도 빈 리스트를 반환해야 한다.ri   z	any-skillN)r   rj   rG   r>   r   r   s       r   .test_missing_eval_axes_file_returns_empty_listzKTestLoadEvalAxesMissingSkill.test_missing_eval_axes_file_returns_empty_list  s=    x +S]; ##K0||r   )ra   rb   rc   r   r}   r~   r   r   rd   r   r   r   r     sF    d QWQcQc hl t Z`ZlZl qu r   r   c                   $    e Zd ZddZddZddZy)TestRecordDefenseNc                 N    dddd}t         j                  |      }|d   dk(  sJ y)u@   방어 기록 시 consecutive_defenses가 1 증가해야 한다.r   r   rO   r7   r8   r6   r7   r   Nr>   record_defenser@   championupdateds      r   3test_record_defense_increments_consecutive_defenseszETestRecordDefense.test_record_defense_increments_consecutive_defenses  s:     %&"#$

 $$X.-.!333r   c                 N    dddd}t         j                  |      }|d   dk(  sJ y)uG   방어 기록 시 consecutive_losses가 0으로 리셋되어야 한다.r   r   rO   r   r8   Nr   r   s      r   -test_record_defense_resets_consecutive_lossesz?TestRecordDefense.test_record_defense_resets_consecutive_losses  s:     %&"#$

 $$X.+,111r   c                 b    dddd}t         j                  |      }|d   dk(  sJ |d   dk(  sJ y)u   record_defense는 원본 dict를 변경하거나 변경된 복사본을 반환해도 무방하지만
        반환값이 올바른 상태여야 한다.r   rO   r   r7   r   r8   Nr   r   s      r   ,test_record_defense_does_not_modify_originalz>TestRecordDefense.test_record_defense_does_not_modify_original   sN     %&"#$

 $$X.-.!333+,111r   r`   )ra   rb   rc   r   r   r   rd   r   r   r   r     s    42
2r   r   c                   $    e Zd ZddZddZddZy)TestRecordLossNc                 N    dddd}t         j                  |      }|d   dk(  sJ y)u>   패배 기록 시 consecutive_losses가 1 증가해야 한다.r   r   rO   r   r8   r   Nr>   record_lossr   s      r   .test_record_loss_increments_consecutive_lossesz=TestRecordLoss.test_record_loss_increments_consecutive_losses  s8     %&"#$

 //(++,111r   c                 N    dddd}t         j                  |      }|d   dk(  sJ y)uI   패배 기록 시 consecutive_defenses가 0으로 리셋되어야 한다.   r   rO   r   r7   Nr   r   s      r   ,test_record_loss_resets_consecutive_defensesz;TestRecordLoss.test_record_loss_resets_consecutive_defenses  s8     %&"#$

 //(+-.!333r   c                 b    dddd}t         j                  |      }|d   dk(  sJ |d   dk(  sJ y)uF   0에서 패배 기록 시 consecutive_losses가 1이 되어야 한다.r   rO   r   r8   r   r7   Nr   r   s      r   test_record_loss_from_zeroz)TestRecordLoss.test_record_loss_from_zero'  sL     %&"#$

 //(++,111-.!333r   r`   )ra   rb   rc   r   r   r   rd   r   r   r   r     s    24	4r   r   c                   $    e Zd ZddZddZddZy)TestStatusStableNc                     dddt        j                  t        j                        j	                         dd}t
        j                  |      }|d   dk(  sJ y)u:   5연속 방어 시 status가 'stable'이 되어야 한다.r   r   rO   r7   r8   r9   r5   r6   r6   stableNr   r   r   r   r   r>   update_champion_statusr   s      r   -test_five_consecutive_defenses_becomes_stablez>TestStatusStable.test_five_consecutive_defenses_becomes_stable9  U     %&"#'(!hll3==?$
 ,,X6x H,,,r   c                     dddt        j                  t        j                        j	                         dd}t
        j                  |      }|d   dk(  sJ y)u3   5 초과 연속 방어도 'stable'이어야 한다.
   r   rO   r   r6   r   Nr   r   s      r   &test_more_than_five_defenses_is_stablez7TestStatusStable.test_more_than_five_defenses_is_stableE  sU     %'"#'(!hll3==?$
 ,,X6x H,,,r   c                     dddt        j                  t        j                        j	                         dd}t
        j                  |      }|d   dk7  sJ y)u2   4연속 방어는 'stable'이 아니어야 한다.   r   rO   r   r6   r   Nr   r   s      r   )test_four_consecutive_defenses_not_stablez:TestStatusStable.test_four_consecutive_defenses_not_stableQ  r   r   r`   )ra   rb   rc   r   r  r  rd   r   r   r   r   8  s    
-
-
-r   r   c                   $    e Zd ZddZddZddZy)TestStatusUnstableNc                     dddt        j                  t        j                        j	                         dd}t
        j                  |      }|d   dk(  sJ y)u<   3연속 패배 시 status가 'unstable'이 되어야 한다.r   r   rO   r   r6   unstableNr   r   s      r   .test_three_consecutive_losses_becomes_unstablezATestStatusUnstable.test_three_consecutive_losses_becomes_unstabled  U     %&"#'(!hll3==?$
 ,,X6x J...r   c                     dddt        j                  t        j                        j	                         dd}t
        j                  |      }|d   dk(  sJ y)u5   3 초과 연속 패배도 'unstable'이어야 한다.r   r   rO   r   r6   r
  Nr   r   s      r   'test_more_than_three_losses_is_unstablez:TestStatusUnstable.test_more_than_three_losses_is_unstablep  r  r   c                     dddt        j                  t        j                        j	                         dd}t
        j                  |      }|d   dk7  sJ y)u4   2연속 패배는 'unstable'이 아니어야 한다.r   r   rO   r   r6   r
  Nr   r   s      r   (test_two_consecutive_losses_not_unstablez;TestStatusUnstable.test_two_consecutive_losses_not_unstable|  r  r   r`   )ra   rb   rc   r  r  r  rd   r   r   r  r  c  s    
/
/
/r   r  c                   $    e Zd ZddZddZddZy)TestStatusManualInterventionNc                     dddt        j                  t        j                        j	                         dd}t
        j                  |      }|d   dk(  sJ y)uT   reinit_count_this_month >= 2 시 'manual_intervention_required'가 되어야 한다.r   r   rO   r   r6   manual_intervention_requiredNr   r   s      r   1test_reinit_count_two_becomes_manual_interventionzNTestStatusManualIntervention.test_reinit_count_two_becomes_manual_intervention  V     %&"#'(!hll3==?$
 ,,X6x $BBBBr   c                     dddt        j                  t        j                        j	                         dd}t
        j                  |      }|d   dk(  sJ y)uL   reinit_count_this_month >= 3도 'manual_intervention_required'여야 한다.r   r   rO   r   r6   r  Nr   r   s      r   3test_reinit_count_three_becomes_manual_interventionzPTestStatusManualIntervention.test_reinit_count_three_becomes_manual_intervention  r  r   c                     dddt        j                  t        j                        j	                         dd}t
        j                  |      }|d   dk7  sJ y)uV   reinit_count_this_month == 1은 'manual_intervention_required'가 아니어야 한다.r   r   rO   r   r6   r  Nr   r   s      r   -test_reinit_count_one_not_manual_interventionzJTestStatusManualIntervention.test_reinit_count_one_not_manual_intervention  r  r   r`   )ra   rb   rc   r  r  r  rd   r   r   r  r    s    
C
C
Cr   r  c                   $    e Zd ZddZddZddZy)TestGracefulDegradationNc                 >    t         j                  ddg d      }|J y)uS   eval_axes가 빈 리스트일 때도 compare_outputs가 정상 동작해야 한다.u   첫 번째 아웃풋입니다.u   두 번째 아웃풋입니다.rM   output_aoutput_b	eval_axesr0   Nr>   compare_outputsr@   r   s     r   .test_compare_outputs_empty_axes_does_not_raisezFTestGracefulDegradation.test_compare_outputs_empty_axes_does_not_raise  s1    $$55#	 % 
 !!!r   c                 Z    t         j                  ddg d      }t        |t              sJ y)u>   eval_axes 빈 리스트 시에도 dict를 반환해야 한다.output Aoutput BrM   r  Nr>   r#  rF   dictr$  s     r   ,test_compare_outputs_empty_axes_returns_dictzDTestGracefulDegradation.test_compare_outputs_empty_axes_returns_dict  s5    $$#	 % 
 &$'''r   c                 @    t         j                  dddgd      }|J y)u@   빈 문자열 아웃풋에도 예외 없이 동작해야 한다. rv   rM   r  Nr"  r$  s     r   )test_compare_outputs_empty_string_outputszATestGracefulDegradation.test_compare_outputs_empty_string_outputs  s3    $$i#	 % 
 !!!r   r`   )ra   rb   rc   r%  r+  r.  rd   r   r   r  r    s    "("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)
TestCompareOutputsStructureNc                 F    t         j                  ddddgd      }d|v sJ y)	u2   비교 결과에 'winner' 키가 있어야 한다.output A contentoutput B contentr-   r.   r+   r  r   Nr"  r$  s     r   #test_compare_outputs_has_winner_keyz?TestCompareOutputsStructure.test_compare_outputs_has_winner_key  s9    $$''#%78(	 % 
 6!!!r   c                 D    t         j                  dddgd      }d|v sJ y)u2   비교 결과에 'reason' 키가 있어야 한다.r'  r(  rv   rM   r  r   Nr"  r$  s     r   #test_compare_outputs_has_reason_keyz?TestCompareOutputsStructure.test_compare_outputs_has_reason_key  5    $$i#	 % 
 6!!!r   c                 D    t         j                  dddgd      }d|v sJ y)u2   비교 결과에 'scores' 키가 있어야 한다.r'  r(  rv   rM   r  scoresNr"  r$  s     r   #test_compare_outputs_has_scores_keyz?TestCompareOutputsStructure.test_compare_outputs_has_scores_key  r7  r   c                 J    t         j                  dddgd      }|d   dv sJ y)	*   winner 값은 'A' 또는 'B'여야 한다.r'  r(  rv   rM   r  r   r   BNr"  r$  s     r   %test_compare_outputs_winner_is_a_or_bzATestCompareOutputsStructure.test_compare_outputs_winner_is_a_or_b  s:    $$i#	 % 
 h:---r   c                 d    t         j                  ddddgd      }t        |d   t              sJ y)	u   scores는 dict여야 한다.r2  r3  r-   r.   r+   r  r9  Nr)  r$  s     r   #test_compare_outputs_scores_is_dictz?TestCompareOutputsStructure.test_compare_outputs_scores_is_dict  s@    $$''#%78(	 % 
 &*D111r   c                     t         j                  dddgd      }|d   }t        |t              sJ |j	                         D ]+  \  }}t        |t
        t        t        f      r"J d| d        y	)
uZ   scores 내 값이 유효한 형식이어야 한다 (list 또는 numeric, 또는 빈 dict).r'  r(  rv   rM   r  r9  zscores[u!   ]는 list 또는 숫자여야 함N)r>   r#  rF   r*  itemsrH   rI   float)r@   r   r9  keyvals        r   ,test_compare_outputs_scores_values_are_validzHTestCompareOutputsStructure.test_compare_outputs_scores_values_are_valid  s    $$i#	 % 
 !&$''' 	iHCcD#u#56h'#Fg8hh6	ir   c                 b    t         j                  dddgd      }t        |d   t              sJ y)u(   reason 값이 문자열이어야 한다.r'  r(  rv   rM   r  r   N)r>   r#  rF   rG   r$  s     r   %test_compare_outputs_reason_is_stringzATestCompareOutputsStructure.test_compare_outputs_reason_is_string  s<    $$i#	 % 
 &*C000r   r`   )
ra   rb   rc   r4  r6  r:  r?  rA  rG  rI  rd   r   r   r0  r0    s&    """.2i1r   r0  c                   X    e Zd Zdedej
                  ddfdZdej
                  ddfdZy)TestGetWorkspaceRootr   rg   r   Nc                     |j                  dt        |             t        j                         }t	        |t
              sJ ||k(  sJ y)u[   WORKSPACE_ROOT 환경변수가 설정되어 있으면 해당 경로를 반환해야 한다.ri   N)rj   rG   r>   get_workspace_rootrF   r   )r@   r   rg   roots       r    test_get_workspace_root_from_envz5TestGetWorkspaceRoot.test_get_workspace_root_from_env/  sC    +S];%%'$%%%xr   c                 v    |j                  dd       t        j                         }t        |t              sJ y)u)   반환값이 Path 타입이어야 한다.ri   F)raisingN)delenvr>   rM  rF   r   )r@   rg   rN  s      r   )test_get_workspace_root_returns_path_typez>TestGetWorkspaceRoot.test_get_workspace_root_returns_path_type6  s4    +U;%%'$%%%r   )ra   rb   rc   r   r}   r~   rO  rS  rd   r   r   rK  rK  .  s=       FL^L^  cg  &VEWEW &\` &r   rK  zoutput_review_ai.pycontent_textc                 L    t               }| |_        t               }|g|_        |S )z%Create a mock Anthropic API response.)r   textcontent)rT  mock_contentmock_responses      r   _mock_anthropic_responserZ  D  s(    ;L$LKM)NMr   1u:   아웃풋 1이 훅 강도와 정보 밀도에서 우수함r  r   r   )r-   r]   r   r   r9  MOCK_COMPARISON_RESULTr   u7   Output A가 훅 강도와 정보 밀도에서 우수함MOCK_COMPARISON_RESULT_ABTuK   v2가 훅 강도와 정보 밀도에서 v1보다 높은 점수를 기록함)improved
comparisonr   MOCK_DELTA_RESULT_IMPROVEDFu:   v2가 v1보다 낮은 점수를 기록하여 개선 실패MOCK_DELTA_RESULT_NOT_IMPROVEDpass)verdictsuggestionsMOCK_CROSS_MODEL_PASSimprove   훅을 더 강하게   정보를 더 구체적으로MOCK_CROSS_MODEL_IMPROVEu$   개선된 최종 챔피언 아웃풋expert_textu   전문가 아웃풋 예시ab_comparisonbenchmark_resultimprovement_applieddelta_resultu   훅 강도 개선이 효과적u/   정보 밀도 상향이 품질 향상에 기여r2   init_process	learningsMOCK_INIT_ENHANCEMENT_RESULTc                   8    e Zd ZdZddZddZddZddZddZy)	TestCompareOutputsAIuJ   compare_outputs_ai 함수의 구조 및 동작을 mock으로 검증한다.Nc                     t        j                  ddt               i      5  ddl}t        |j
                  _        |j                  ddddgd	
      }d|v sJ d|v sJ d|v sJ 	 ddd       y# 1 sw Y   yxY w)uZ   compare_outputs_ai가 winner, reason, scores 키를 포함한 dict를 반환해야 한다.sys.modulesoutput_review_air   N   첫 번째 아웃풋   두 번째 아웃풋r-   r]   r+   r  r   r   r9  r   r*  r   ry  r^  compare_outputs_aireturn_valuer@   orair   s      r   1test_compare_outputs_ai_returns_correct_structurezFTestCompareOutputsAI.test_compare_outputs_ai_returns_correct_structure  s    ZZ(:IK'HI 	&+3LD##0,,//'9,	 - F v%%%v%%%v%%%	& 	& 	&s   AA..A7c                     t        j                  ddt               i      5  ddl}t        |j
                  _        |j                  dddgd	      }|d
   dv sJ 	 ddd       y# 1 sw Y   yxY w)r<  rx  ry  r   Nr'  r(  r-   rM   r  r   r=  r|  r  s      r   (test_compare_outputs_ai_winner_is_a_or_bz=TestCompareOutputsAI.test_compare_outputs_ai_winner_is_a_or_b  sv    ZZ(:IK'HI 	2+3LD##0,,##'.'	 - F (#z111	2 	2 	2s   9A$$A-c                    t        j                  ddt               i      5  ddl}t        |j
                  _        |j                  ddddgd	
      }|d   }t        |t              sJ |j                         D ]M  \  }}t        |t              sJ t        |t              sJ t        |      dk(  sJ t        d |D              rMJ  	 ddd       y# 1 sw Y   yxY w)uI   scores는 평가 축별 [a_score, b_score] 리스트 구조여야 한다.rx  ry  r   Nr'  r(  r-   r]   rM   r  r9  r   c              3   H   K   | ]  }t        |t        t        f        y wr   )rF   rI   rD  )r   ss     r   r   zYTestCompareOutputsAI.test_compare_outputs_ai_scores_structure_per_axis.<locals>.<genexpr>  s     K1:a#u6Ks    ")r   r*  r   ry  r^  r}  r~  rF   rC  rG   rH   r   all)r@   r  r   r9  	axis_name
score_pairs         r   1test_compare_outputs_ai_scores_structure_per_axiszFTestCompareOutputsAI.test_compare_outputs_ai_scores_structure_per_axis  s    ZZ(:IK'HI 	L+3LD##0,,##'9'	 - F H%Ffd+++)/ L%	:!)S111!*d333:!+++K
KKKK	L	L 	L 	Ls   B$CCCc                    ddi d}t        j                  ddt               i      5  ddl}||j                  _        |j	                  dd	g d
      }|J d|v sJ t        |d   t              sJ 	 ddd       y# 1 sw Y   yxY w)uD   eval_axes가 빈 리스트여도 graceful하게 동작해야 한다.r   u+   eval_axes 없이 전반적 품질로 판단r\  rx  ry  r   Nr'  r(  rM   r  r   r9  )r   r*  r   ry  r}  r~  rF   )r@   empty_axes_resultr  r   s       r   0test_compare_outputs_ai_empty_eval_axes_gracefulzETestCompareOutputsAI.test_compare_outputs_ai_empty_eval_axes_graceful  s     C-

 ZZ(:IK'HI 	6+3DD##0,,##'	 - F %%%v%%%fX.555	6 	6 	6s   A
A;;Bc                 :   t               }t        d      |j                  _        t	        j
                  dd|i      5  ddl}t        j                  t        d      5  |j                  dd	d
gd       ddd       ddd       y# 1 sw Y   xY w# 1 sw Y   yxY w)uH   ANTHROPIC_API_KEY가 없을 때 EnvironmentError가 발생해야 한다.z1ANTHROPIC_API_KEY environment variable is not setrx  ry  r   NANTHROPIC_API_KEY)matchr'  r(  r-   rM   r  )	r   EnvironmentErrorr}  side_effectr   r*  ry  r}   raises)r@   mock_moduler  s      r   ;test_compare_outputs_ai_no_api_key_raises_environment_errorzPTestCompareOutputsAI.test_compare_outputs_ai_no_api_key_raises_environment_error  s    k5E?6
&&2 ZZ(:K'HI 		+/7JK ''''+n+	 ( 		 		 		 		s#    BB4BB	
BBr`   )	ra   rb   rc   __doc__r  r  r  r  r  rd   r   r   rv  rv    s    T&$2 L,6.r   rv  c                        e Zd ZdZddZddZy) TestComparisonOrderRandomizationuA   A/B 순서 랜덤화 및 winner 매핑 정확성을 검증한다.Nc                 `   dddddgid}dddddgid}t               }||g|j                  _        t        j                  d	d
|i      5  ddl}|j                  dddgd      }|j                  dddgd      }|d   |d   h}t        |      dk(  sJ d       	 ddd       y# 1 sw Y   yxY w)ui   여러 번 호출 시 A/B 순서가 섞이는지 확인한다 (mock으로 순서 변화 시뮬레이션).r   u   A가 더 나음r-   r   r   r\  r>  u   B가 더 나음rx  ry  r   Nzoutput Xzoutput YrM   r  r   r   u+   랜덤화로 인해 winner가 달라야 함)r   r}  r  r   r*  ry  r   )r@   result_a_winsresult_b_winsr  r  result1result2winnerss           r   'test_randomization_produces_mixed_orderzHTestComparisonOrderRandomization.test_randomization_produces_mixed_order  s     '#aV,)
 '#aV,)
  k6C]5S&&2ZZ(:K'HI 	T+--##'.'	 . G --##'.'	 . G x('(*;<Gw<1$S&SS$%	T 	T 	Ts   
AB$$B-c                    dddddgid}t               }||j                  _        t        j                  dd|i      5  d	d
l}|j                  dddgd      }|d   dv sJ d|v sJ d|v sJ 	 d
d
d
       y
# 1 sw Y   y
xY w)uS   순서가 바뀌어도 winner가 올바르게 원래 A/B로 매핑되어야 한다.r[  u   position 1이 더 나음r-   r   r   r\  rx  ry  r   Nzoutput A textzoutput B textrM   r  r   )r   r>  r[  2r   r9  )r   r}  r~  r   r*  ry  )r@   position_resultr  r  r   s        r   9test_winner_correctly_mapped_to_original_ab_after_shufflezZTestComparisonOrderRandomization.test_winner_correctly_mapped_to_original_ab_after_shuffle  s     0#aV,+
  k6E&&3ZZ(:K'HI 	&+,,(('.'	 - F (#';;;;v%%%v%%%	& 	& 	&s   0A88Br`   )ra   rb   rc   r  r  r  rd   r   r   r  r    s    K$TL!&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)	TestInitEnhancementuA   run_init_enhancement 전체 플로우를 mock으로 검증한다.Nc                    t               }t        |j                  _        t	        j
                  dd|i      5  ddl}|j                  ddddgd	d
      }|d   }d|v sJ d|v sJ d|v sJ 	 ddd       y# 1 sw Y   yxY w)uc   online_expert 모드: A/B 비교 → expert 벤치마크 → 개선 → delta 검증 → 챔피언.rx  ry  r   Nrz  r{  r-   r]   r+   rQ   r  r   r!  r0   benchmark_methodrr  rm  rn  ro  )r   rt  run_init_enhancementr~  r   r*  ry  )r@   r  r  r   	init_procs        r   !test_online_expert_mode_full_flowz5TestInitEnhancement.test_online_expert_mode_full_flowF  s    k8T((5ZZ(:K'HI 	6+..//'9,!0 / F ~.I"i///%222(I555	6 	6 	6s   4A66A?c                 *   dt         t        dt        ddgd}t               }||j                  _        t        j                  dd|i      5  dd	l}|j	                  d
dddgdd      }|d   }d|v sJ |d   }d|v sJ 	 d	d	d	       y	# 1 sw Y   y	xY w)u`   cross_model 모드: A/B 비교 → cross-model verify → 개선 → delta 검증 → 챔피언.u*   cross-model 검증 후 개선된 챔피언Trl  u&   cross-model 검증으로 개선 확인rq  rx  ry  r   Nrz  r{  r-   r]   r+   cross_modelr  rr  rn  rd  )	r^  rj  ra  r   r  r~  r   r*  ry  )r@   cross_model_resultr  r  r   r  	benchmarks          r   test_cross_model_mode_full_flowz3TestInitEnhancement.test_cross_model_mode_full_flow\  s      L!:$<'+ :	 CC	.
  k8J((5ZZ(:K'HI 	*+..//'9,!. / F ~.I%222!"45I	)))	* 	* 	*s   3B		Bc                 
   t               }t        |j                  _        t	        j
                  dd|i      5  ddl}|j                  dddgdd	
      }d|v sJ t        |d   t              sJ 	 ddd       y# 1 sw Y   yxY w)u9   반환 구조에 champion_output 키가 있어야 한다.rx  ry  r   Nr'  r(  r-   rM   rQ   r  r2   )	r   rt  r  r~  r   r*  ry  rF   rG   r@   r  r  r   s       r   1test_run_init_enhancement_returns_champion_outputzETestInitEnhancement.test_run_init_enhancement_returns_champion_output}  s    k8T((5ZZ(:K'HI 	>+..##'.'!0 / F %...f%67===	> 	> 	>   7A99Bc                 
   t               }t        |j                  _        t	        j
                  dd|i      5  ddl}|j                  dddgdd	
      }d|v sJ t        |d   t
              sJ 	 ddd       y# 1 sw Y   yxY w)u6   반환 구조에 init_process 키가 있어야 한다.rx  ry  r   Nr'  r(  r-   rM   rQ   r  rr  )r   rt  r  r~  r   r*  ry  rF   r  s       r   .test_run_init_enhancement_returns_init_processzBTestInitEnhancement.test_run_init_enhancement_returns_init_process  s    k8T((5ZZ(:K'HI 	<+..##'.'!0 / F "V+++f^4d;;;	< 	< 	<r  c                 
   t               }t        |j                  _        t	        j
                  dd|i      5  ddl}|j                  dddgdd	
      }d|v sJ t        |d   t              sJ 	 ddd       y# 1 sw Y   yxY w)u3   반환 구조에 learnings 키가 있어야 한다.rx  ry  r   Nr'  r(  r-   rM   rQ   r  rs  )	r   rt  r  r~  r   r*  ry  rF   rH   r  s       r   +test_run_init_enhancement_returns_learningsz?TestInitEnhancement.test_run_init_enhancement_returns_learnings  s    k8T((5ZZ(:K'HI 	9+..##'.'!0 / F &(((f[14888	9 	9 	9r  r`   )	ra   rb   rc   r  r  r  r  r  r  rd   r   r   r  r  C  s    K6,*B>&<&9r   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)	TestDeltaVerifyuE   delta_verify 함수의 improved True/False 케이스를 검증한다.Nc                     t               }t        |j                  _        t	        j
                  dd|i      5  ddl}|j                  ddddgd	
      }|d   du sJ 	 ddd       y# 1 sw Y   yxY w)uB   v2가 v1보다 나은 경우 improved=True를 반환해야 한다.rx  ry  r   N   원본 아웃풋u   개선된 아웃풋r-   r]   rM   v1v2r!  r0   r_  Tr   ra  delta_verifyr~  r   r*  ry  r  s       r   1test_delta_verify_v2_better_returns_improved_truezATestDeltaVerify.test_delta_verify_v2_better_returns_improved_true  s~    k0J  -ZZ(:K'HI 
	.+&&%('9'	 ' F *%---
	. 
	. 
	.   %A''A0c                     t               }t        |j                  _        t	        j
                  dd|i      5  ddl}|j                  ddddgd	
      }|d   du sJ 	 ddd       y# 1 sw Y   yxY w)uU   v2가 v1보다 못한 경우 improved=False를 반환하고 fallback이 필요하다.rx  ry  r   Nu   더 나은 원본 아웃풋u   품질이 저하된 아웃풋r-   r]   rM   r  r_  F)r   rb  r  r~  r   r*  ry  r  s       r   1test_delta_verify_v2_worse_returns_improved_falsezATestDeltaVerify.test_delta_verify_v2_worse_returns_improved_false  s~    k0N  -ZZ(:K'HI 
	/+&&02'9'	 ' F *%...
	/ 
	/ 
	/r  c                     t               }t        |j                  _        t	        j
                  dd|i      5  ddl}|j                  dddgd	      }d
|v sJ 	 ddd       y# 1 sw Y   yxY w)u2   반환 구조에 improved 키가 있어야 한다.rx  ry  r   Nr  r  rv   rM   r  r_  r  r  s       r   &test_delta_verify_returns_improved_keyz6TestDeltaVerify.test_delta_verify_returns_improved_key  sv    k0J  -ZZ(:K'HI 
	(+&&")'	 ' F '''
	( 
	( 
	(   !A##A,c                    t               }t        |j                  _        t	        j
                  dd|i      5  ddl}|j                  dddgd	      }d
|v sJ t        |d
   t
              sJ 	 ddd       y# 1 sw Y   yxY w)u4   반환 구조에 comparison 키가 있어야 한다.rx  ry  r   Nr  r  rv   rM   r  r`  )r   ra  r  r~  r   r*  ry  rF   r  s       r   (test_delta_verify_returns_comparison_keyz8TestDeltaVerify.test_delta_verify_returns_comparison_key  s    k0J  -ZZ(:K'HI 	:+&&")'	 ' F  6)))f\2D999	: 	: 	:   6A88Bc                    t               }t        |j                  _        t	        j
                  dd|i      5  ddl}|j                  dddgd	      }d
|v sJ t        |d
   t              sJ 	 ddd       y# 1 sw Y   yxY w)u0   반환 구조에 reason 키가 있어야 한다.rx  ry  r   Nr  r  rv   rM   r  r   )	r   ra  r  r~  r   r*  ry  rF   rG   r  s       r   $test_delta_verify_returns_reason_keyz4TestDeltaVerify.test_delta_verify_returns_reason_key  s    k0J  -ZZ(:K'HI 	5+&&")'	 ' F v%%%fX.444	5 	5 	5r  r`   )	ra   rb   rc   r  r  r  r  r  r  rd   r   r   r  r    s    O."/"(":$5r   r  c                   (    e Zd ZdZddZddZddZy)TestGracefulDegradation_Phase2u:   Phase 2 graceful degradation 시나리오를 검증한다.Nc                 h   dt         dddddgd}t               }d|j                  _        ||j                  _        t        j                  dd|i      5  d	dl}|j                  d
d      }|J |j	                  dddgd
d      }|d   d   J |d   d   du sJ 	 ddd       y# 1 sw Y   yxY w)uV   WebSearch 결과가 None일 때 A/B 선택본 그대로 챔피언이 되어야 한다.   A/B 비교 winner 아웃풋NFrl  u<   expert 검색 실패로 A/B winner를 챔피언으로 지정rq  rx  ry  r   rM   
some topicr0   topicr'  r(  r-   rQ   r  rr  rn  ro  r^  r   search_expert_outputr~  r  r   r*  ry  )r@   no_benchmark_resultr  r  expertr   s         r   5test_websearch_none_result_uses_ab_winner_as_championzTTestGracefulDegradation_Phase2.test_websearch_none_result_uses_ab_winner_as_champion  s      =!:$(', $	 YY	/
  k8<((58K((5ZZ(:K'HI 	J+ ..,l.[F>!> ..##'.'!0 / F .)*<=EEE.)*?@EIII!	J 	J 	Js   A	B((B1c                    dt         dg ddddddgd	}t               }t        d
      |j                  _        ||j
                  _        t        j                  dd|i      5  ddl	}t        j                  t              5  |j                  dd       ddd       |j                  dddgdd      }|d   d   }|J |j                  d      dk(  sJ 	 ddd       y# 1 sw Y   LxY w# 1 sw Y   yxY w)uN   cross_model_verify가 예외 발생 시 self-review로 대체되어야 한다.u   self-review fallback 챔피언rc  self_review)rd  re  fallbackFNrl  u8   cross_model_verify 오류로 self-review fallback 적용rq  zCross model verification failedrx  ry  r   zsome outputrM   rE   r0   r'  r(  r-   r  r  rr  rn  r  )r^  r   RuntimeErrorcross_model_verifyr  r  r~  r   r*  ry  r}   r  get)r@   fallback_resultr  r  r   r  s         r   ;test_cross_model_verify_exception_falls_back_to_self_reviewzZTestGracefulDegradation_Phase2.test_cross_model_verify_exception_falls_back_to_self_reviewC  s$     @!:06rWd$e', $	 UU	+
  k5ABc5d&&28G((5ZZ(:K'HI 	>+ |, W''}'VW ..##'.'!. / F ~./ABI(((==,===#	> 	>W W		> 	>s%   "C+ CAC+C(	$C++C4c                 ^   dt         ddiddddgd}t               }d|j                  _        ||j                  _        t        j                  d	d
|i      5  ddl}|j                  dd      }|dk(  sJ |j	                  dddgdd      }|d   d   du sJ 	 ddd       y# 1 sw Y   yxY w)uc   expert 검색 결과가 빈 문자열일 때 A/B 선택본 그대로 챔피언이 되어야 한다.r  rk  r-  FNrl  uB   expert 결과가 비어있어 A/B winner를 챔피언으로 지정rq  rx  ry  r   rM   r  r  r'  r(  r-   rQ   r  rr  ro  r  )r@   no_improvement_resultr  r  r  r   s         r   3test_expert_empty_string_uses_ab_winner_as_championzRTestGracefulDegradation_Phase2.test_expert_empty_string_uses_ab_winner_as_championi  s      =!:%2B$7', $	 __	1
  k8:((58M((5ZZ(:K'HI 	J+..,l.[FR<<..##'.'!0 / F .)*?@EIII	J 	J 	Js   AB##B,r`   )ra   rb   rc   r  r  r  r  rd   r   r   r  r    s    D"JH$>LJ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)	TestCrossModelVerifyu;   cross_model_verify 스텁의 반환 구조를 검증한다.Nc                     t               }t        |j                  _        t	        j
                  dd|i      5  ddl}|j                  dd      }d|v sJ 	 ddd       y# 1 sw Y   yxY w)	u1   반환 구조에 verdict 키가 있어야 한다.rx  ry  r   N   테스트 아웃풋 텍스트rM   r  rd  r   rf  r  r~  r   r*  ry  r  s       r   +test_cross_model_verify_returns_verdict_keyz@TestCrossModelVerify.test_cross_model_verify_returns_verdict_key  sn    k6K&&3ZZ(:K'HI 	'+,,6' - F
 &&&	' 	' 	's   A  A)c                    t               }t        |j                  _        t	        j
                  dd|i      5  ddl}|j                  dd      }d|v sJ t        |d   t              sJ 	 ddd       y# 1 sw Y   yxY w)	u5   반환 구조에 suggestions 키가 있어야 한다.rx  ry  r   Nr  rM   r  re  )	r   rf  r  r~  r   r*  ry  rF   rH   r  s       r   /test_cross_model_verify_returns_suggestions_keyzDTestCrossModelVerify.test_cross_model_verify_returns_suggestions_key  s    k6K&&3ZZ(:K'HI 		;+,,6' - F
 !F***f]3T:::		; 		; 		;s   3A55A>c                     t               }t        |j                  _        t	        j
                  dd|i      5  ddl}|j                  dd      }|d   d	v sJ 	 ddd       y# 1 sw Y   yxY w)
u4   verdict 값은 'pass' 또는 'improve'여야 한다.rx  ry  r   Nr  rM   r  rd  )rc  rg  r  r  s       r   2test_cross_model_verify_verdict_is_pass_or_improvezGTestCrossModelVerify.test_cross_model_verify_verdict_is_pass_or_improve  st    k6K&&3ZZ(:K'HI 	<+,,6' - F
 )$(;;;;	< 	< 	<r  c                     t               }t        |j                  _        t	        j
                  dd|i      5  ddl}|j                  dd      }|d   d	k(  sJ 	 ddd       y# 1 sw Y   yxY w)
u6   현재 스텁은 항상 'pass'를 반환해야 한다.rx  ry  r   Nu   어떤 아웃풋이든rM   r  rd  rc  r  r  s       r   )test_cross_model_verify_stub_returns_passz>TestCrossModelVerify.test_cross_model_verify_stub_returns_pass  ss    k6K&&3ZZ(:K'HI 	/+,,/' - F
 )$...	/ 	/ 	/   "A$$A-c                     t               }t        |j                  _        t	        j
                  dd|i      5  ddl}|j                  dd      }|d   g k(  sJ 	 ddd       y# 1 sw Y   yxY w)	u?   'pass' 결과는 빈 suggestions 리스트를 가져야 한다.rx  ry  r   Nu   테스트 아웃풋rM   r  re  r  r  s       r   2test_cross_model_verify_pass_has_empty_suggestionszGTestCrossModelVerify.test_cross_model_verify_pass_has_empty_suggestions  ss    k6K&&3ZZ(:K'HI 	/+,,,' - F
 -(B...	/ 	/ 	/r  r`   )	ra   rb   rc   r  r  r  r  r  r  rd   r   r   r  r    s    E'; <//r   r  c                   0    e Zd ZdZddZddZddZddZy)TestGenerateImprovedOutputu:   generate_improved_output 함수의 동작을 검증한다.Nc                     d}t               }||j                  _        t        j                  dd|i      5  ddl}|j                  |g ddg      }||k(  sJ 	 ddd       y# 1 sw Y   yxY w)	uE   suggestions가 비어있을 때 원본 출력을 반환해야 한다.u   원본 아웃풋 텍스트rx  ry  r   NrM   r-   r   re  r0   r!  )r   generate_improved_outputr~  r   r*  ry  )r@   original_textr  r  r   s        r   'test_empty_suggestions_returns_originalzBTestGenerateImprovedOutput.test_empty_suggestions_returns_original  s{    4k<I,,9ZZ(:K'HI 
	++22&''.	 3 F ]***
	+ 
	+ 
	+s   "A""A+c                    d}t               }||j                  _        t        j                  dd|i      5  ddl}|j                  dddgd	d
dg      }|j                  j                          ||k(  sJ 	 ddd       y# 1 sw Y   yxY w)uP   suggestions가 있을 때 AI 개선 호출이 정상 동작해야 한다 (mock).u#   AI가 개선한 아웃풋 텍스트rx  ry  r   Nr  rh  ri  r+   r-   r]   r  )r   r  r~  r   r*  ry  assert_called_once)r@   improved_textr  r  r   s        r   7test_generate_improved_output_with_suggestions_calls_aizRTestGenerateImprovedOutput.test_generate_improved_output_with_suggestions_calls_ai  s    =k<I,,9ZZ(:K'HI 	++22+35TU,'9	 3 F ))<<>]***	+ 	+ 	+s   ?A??Bc                     t               }d|j                  _        t        j                  dd|i      5  ddl}|j                  ddgdd	g
      }t        |t              sJ 	 ddd       y# 1 sw Y   yxY w)uD   generate_improved_output은 항상 문자열을 반환해야 한다.u   개선된 문자열rx  ry  r   Nu   원본u   개선 제안rM   rv   r  )r   r  r~  r   r*  ry  rF   rG   r  s       r   ,test_generate_improved_output_returns_stringzGTestGenerateImprovedOutput.test_generate_improved_output_returns_string  s{    k<Q,,9ZZ(:K'HI 
	++22!,-'")	 3 F fc***
	+ 
	+ 
	+s   .A,,A5c                 &   d}t        |      }t               }|j                  d   j                  |j                  _        t        j                  dd|i      5  ddl}|j	                  dddgd	d
dg      }||k(  sJ 	 ddd       y# 1 sw Y   yxY w)uT   Anthropic 클라이언트 mock을 통해 AI 개선 호출이 이루어져야 한다.u*   Anthropic AI가 개선한 최종 아웃풋r   rx  ry  Nr  u   구체적인 수치 추가u   감성적 표현 강화r+   r-   r]   r  )	rZ  r   rW  rV  r  r~  r   r*  ry  )r@   improved_contentrY  r  r  r   s         r   1test_generate_improved_output_uses_anthropic_mockzLTestGenerateImprovedOutput.test_generate_improved_output_uses_anthropic_mock   s    G01ABk<I<Q<QRS<T<Y<Y,,9ZZ(:K'HI 
	.+22+9;TU,'9	 3 F ----
	. 
	. 
	.s   %BBr`   )ra   rb   rc   r  r  r  r  r  rd   r   r   r  r    s    D+&+*+".r   r  )Dr  importlib.util	importlibr$   sysr   r   pathlibr   typingr   unittest.mockr   r   r}   __file__parent_SCRIPTS_DIR_HELPERS_PATHutilspec_from_file_location_helpers_specloadermodule_from_specr>   modulesexec_module
_MAIN_PATH
_main_specormr*  rG   r   rH   r'   r)   rf   r   r   r   r   r   r   r   r   r   r  r  r  r0  rK  _AI_MODULE_PATHrZ  r]  __annotations__r^  ra  rb  rf  rj  rt  rv  r  r  r  r  r  r  rd   r   r   <module>r     s     
 '   *  H~$$++ 99667NP]^ ]%9%9%E EEnn%%m4'*# $        % ..
^^33OZP
*"3"3"? ??nn%%j1"O  
    c "T d39o $ Dd3i4H T `. `.P33 33v 0;% ;%F 09; 9;B#% #%V :2 2N4 4L#- #-V#/ #/V#C #CV" "JH1 H1`& && !663 9  J!fQ* S#X  G!fQ- 4S>  "#Q1a&A[. DcN  "#Q1a&AJ2 S#X  ) tCH~  *,KL, $sCx.  >2*,HI#2	 34ef	0 d38n 	"a aRJ& J&dq9 q9rX5 X5@kJ kJfM/ M/jO. O.r   