
     ja                    N   d Z ddlmZ ddlZddlZddlmZ ddlmZm	Z	m
Z
 ddlZ ee      j                         j                  d   Z ee      ej"                  vr"ej"                  j%                  d ee             ddlmZmZmZ ddlmZmZmZmZmZ d!d"d	Z G d
 d      Z G d de      Z G d d      Z dddddd	 	 	 	 	 	 	 	 	 	 	 d#dZ!dZ"d$dZ#d$dZ$d$dZ%d%dZ&d&dZ'd'dZ(d$dZ)	 	 	 	 	 	 d&dZ*	 	 	 	 d'dZ+	 	 	 	 	 	 d&dZ,	 	 	 	 	 	 d&dZ-	 	 	 	 	 	 d&dZ.	 	 	 	 d'd Z/y)(u%  anu_v2.tests.test_replacement_pr_runner_2537 — 회귀 7건 (회장 §명시 1:1).

회귀 케이스 (회장 §명시):
  1. clean PR contamination 0       — expected_files == diff → contaminated=False
  2. extra files contamination       — expected_files 외 파일 존재 → contaminated=True
  3. missing files contamination     — expected_files 일부 부재 → contaminated=True
  4. original PR 보존                — preserve 후 close/abort gh 호출 0 + OPEN 유지
  5. clean replacement 생성          — clean branch + 새 PR + queue ready
  6. replacement 실패 → Critical 7종 — 단계별 classify_failure 매핑 정확
  7. executor 인터페이스 contract    — replacement_pr_required 키 제공

본 회귀는 anu_v2/* 모듈만 import 한다 (one-way isolation).
    )annotationsN)Path)AnyMappingSequence   )CRITICAL_CODES CRITICAL_DIFF_REPLACEMENT_FAILEDCRITICAL_REPLACEMENT_FAILED)ContaminationReportPreservationRecordReplacementFailureReplacementPRRunnerReplacementResultc                4    t        j                  g | ||      S )N)args
returncodestdoutstderr)
subprocessCompletedProcess)r   r   r   s      C/home/jay/workspace/anu_v2/tests/test_replacement_pr_runner_2537.py_cpr   ,   s    &&B:f]cdd    c                  .    e Zd ZdZdddZ	 	 	 	 	 	 ddZy)_GitRecorderu\   git_runner mock — 각 호출의 인자/env 를 기록하고 시나리오별 응답 반환.Nc                F    g | _         |rt        |      | _        y g | _        y N)callslist
_scenarios)self	scenarioss     r   __init__z_GitRecorder.__init__3   s    CE
-6$y/Br   c                    | j                   j                  t        |      t        |xs i       f       | j                  r| j                  j                  d      S t               S )Nr   )r   appendtupledictr!   popr   )r"   r   envs      r   __call__z_GitRecorder.__call__7   sJ    
 	

5;SYB89????&&q))ur   r   )r#   z(list[subprocess.CompletedProcess] | NonereturnNone)r   zSequence[str]r*   zMapping[str, str] | Noner,   subprocess.CompletedProcess)__name__
__module____qualname____doc__r$   r+    r   r   r   r   0   s+    f? & 
%	r   r   c                      e Zd ZdZy)_GhRecorderuE   gh_runner mock — git 과 동일 패턴, 별도 클래스로 명시.N)r/   r0   r1   r2   r3   r   r   r5   r5   B   s    Or   r5   c                      e Zd ZddZddZy)_AuditRecorderc                    g | _         y r   )records)r"   s    r   r$   z_AuditRecorder.__init__G   s	    -/r   c                L    | j                   j                  t        |             y r   )r9   r&   r(   )r"   records     r   r+   z_AuditRecorder.__call__J   s    DL)r   Nr,   r-   )r;   zMapping[str, Any]r,   r-   )r/   r0   r1   r$   r+   r3   r   r   r7   r7   F   s    0*r   r7   BOT_GITHUB_TOKEN)
git_runner	gh_runneraudit_writer
audit_rootbot_token_envc                    | xs
 t               }|xs
 t               }|xs
 t               }|xs t        d      }t	        |||||      }	|	|||fS )Nz/tmp/anu_v2_audit_test)r?   r>   r@   rA   rB   )r   r5   r7   r   r   )
r>   r?   r@   rA   rB   gitghauditrootrunners
             r   _make_runnerrI   N   sc     
&C		#kmB,N,E767D #F 3E!!r   )anu_v2/replacement_pr_runner.py/anu_v2/tests/test_replacement_pr_runner_2537.pyc                    t               \  } }}}| j                  t        t              t        t                    }t	        |t
              sJ |j                  du sJ |j                  dk(  sJ |j                  dk(  sJ y )Noriginal_pr_diffexpected_filesFr3   )	rI   detect_contaminationr    EXPECTED_FILES
isinstancer   contaminatedextra_filesmissing_filesrH   _reports      r   0test_detect_contamination_clean_when_files_matchrY   k   s    "nOFAq!((n-N+ ) F f1222%'''###2%%%r   c                     t               \  } }}}| j                  t        t              ddgz   t        t                    }|j                  du sJ |j
                  dk(  sJ |j                  dk(  sJ y )Nscripts/dispatch.pyutils/some_helper.pyrM   T)r[   r\   r3   )rI   rP   r    rQ   rS   rT   rU   rV   s      r   -test_detect_contamination_extra_files_flaggedr]   x   s    "nOFAq!((n-!"1
 

 N+ ) F $&&&!PPPP2%%%r   c                     t               \  } }}}| j                  t        d   gt        t                    }|j                  du sJ |j
                  dk(  sJ |j                  t        d   fk(  sJ y )Nr   rM   Tr3      )rI   rP   rQ   r    rS   rT   rU   rV   s      r   /test_detect_contamination_missing_files_flaggedr`      s    "nOFAq!(((+,N+ ) F $&&&###N1$5#7777r   c                    t        |       \  }}}}|j                  d      }t        |t              sJ |j                  dk(  sJ |j
                  dk(  sJ |j                  g k(  sJ |j                  g k(  sJ t        |j                        dk(  sJ |j                  d   d   dk(  sJ |j                  d   d   d	k(  sJ |j                  d   d
   dk(  sJ |j                  j                  t        |             sJ y )N)rA   T   )	pr_numberOPENr_   r   prdecisionORIGINAL_PR_PRESERVEDnoteu%   OPEN 유지 — close/abort 미수행)rI   preserve_original_prrR   r   original_prpreserved_stater   lenr9   
audit_path
startswithstr)tmp_pathrH   rD   rE   rF   r;   s         r   6test_preserve_original_pr_open_state_no_close_or_abortrq      s   )X>FCU((2(6Ff0111###!!V+++99??88r>>u}}"""==D!R'''==J'+BBBB==F#'NNNN''H666r   c                N   | j                  dd       t               t               t               t               t               t        d      g}t        d      g}t        |      }t        |      }t	        |||      \  }}}}|j                  dt        t              d	      }	t        |	t              sJ |	j                  d
k(  sJ |	j                  dk(  sJ |	j                  dk(  sJ |	j                  du sJ |j                  |j                  z   D ]1  \  }}
|
j                  d      dk(  sJ |
j                  d      dk(  r1J  |j                  |j                  z   D ](  \  }}dj!                  |      }d|vsJ d|vsJ d|vr(J  |j                  D ]0  \  }}|r|d   nd}t#        |      dkD  r|d   nd}|dk(  s*|dk(  r0J  |j$                  D cg c]  }|j                  d      dk(  s| }}t#        |      dk(  sJ |d   d   dk(  sJ |d   d   d
k(  sJ y c c}w )Nr=   bot-test-tokenzabc1234deadbeef
r   z&https://github.com/owner/repo/pull/91
r>   r?   rA   P   task/task-2537-dev4-cleanrj   rO   clean_branch_name[   abc1234deadbeefTGH_TOKENGITHUB_TOKEN z--adminz--forcez--rebaser    r_   re   createrf   REPLACEMENT_PR_CREATEDrj   replacement_pr)setenvr   r   r5   rI   create_clean_replacementr    rQ   rR   r   r   	clean_shaclean_branchmerge_queue_readyr   getjoinrl   r9   )monkeypatchrp   git_scenariosgh_scenariosrD   rE   rH   rW   rF   resultr*   r   joinedheadactionrsuccess_recordss                    r   %test_create_clean_replacement_successr      sv   )+;< 	&'M 	<=L }
%C	\	"B&#PXYFAq%,,N+5 - F
 f/000  B&&&0000"====##t+++ ))bhh& ;3wwz"&6666ww~&*::::;
 99rxx' (a$&&&&&&'''	( 88 &atAwBIMar4<X%%%	& #(--aQ1553DH`3`qaOa1$$$1m,2221./2555 bs   H"1H"c                   t               \  }}}}|j                  t        dd            \  }}|t        k(  sJ |du sJ |t        v sJ |j                  t        dd            \  }}|t        k(  sJ |t        v sJ |j                  t        dd            \  }}|t
        k(  sJ |du sJ |t        v sJ | j                  d	d
       t               t               t               t               t        dd      g}t        |      }t        |      \  }}}}|j                  dt        t              d      }t        |t              sJ |j                  dk(  sJ |j                  |      \  }	}|	t        k(  sJ y )Npushgit_push_failed)stagereasonT	pr_creategh_pr_create_failed	bot_tokenbot_token_unavailabler=   rs   r_   zremote rejectedr   r   r>   rv   rw   rx   )rI   classify_failurer   r   r	   r
   r   r   r   r   r    rQ   rR   r   )
r   rH   rW   codeis_criticalr   rD   runner2failurecode2s
             r   ,test_classify_failure_maps_to_critical_sevenr      s   "nOFAq!//0ABD+ ....$>!!! %%5JKGD! ....>!!! //5LMD+ 3333$>!!! )+;<q!23M }
%C#s3GQ1..N+5 / G
 g1222==F"""''0HE1////r   c                    t        ddd      } t        j                  | dt        t              d      }t        |j                               dd	hk(  sJ |d   du sJ |d	   }|d
   dk(  sJ |d   t        t              k(  sJ |d   dk(  sJ |d   d   du sJ |d   d   dgk(  sJ t        ddd      }t        j                  |dt        t              d      }|d   du sJ y )NT)scripts/leak.pyr3   )rS   rT   rU   rv   rw   )contaminationrj   rO   ry   replacement_pr_requiredreplacement_pr_runner_inputrj   rO   ry   r   rS   rT   r   F)r   r   build_executor_contractr    rQ   setkeys)contamination_dirtycontract_dirtypayloadcontamination_cleancontract_cleans        r    test_executor_contract_dict_keysr     sK   -(
 )@@)N+5	N ~""$%!%*    34<<<:;G=!R'''#$^(<<<<&'+FFFF?#N3t;;;?#M27H6IIII .
 )@@)N+5	N 34===r   c                z   | j                  dd       d}t               t               t               t               t               t        d      g}t        d      g}t        |      }t        |      }t	        |||      \  }}}}|j                  dt        |      d	
      }	t        |	t              sJ d}
|j                  D cg c]%  \  }}t        |      dk\  r|d   dk(  r
|d   |
k(  r|' }}}t        |      dk(  sJ dt        |       d       |d   }|d   dk(  sJ t        |dd       |k(  sJ yc c}}w )u\  expected_files N 개를 단일 git checkout 호출로 stage 한다.

    Gemini 1차 review (review_id=4259255425, 2026-05-10) medium 권고:
      - 기존: expected_files 루프 → git checkout 개별 호출 N회
      - 개선: 단일 호출 1회 (subprocess overhead 절감)
    본 회귀는 batched 동작을 박제하여 회귀 방지.
    r=   rs   )rJ   rK   zanu_v2/__init__.pyz	cafef00d
rt   z&https://github.com/owner/repo/pull/92
ru   rv   ztask/task-2537-dev4-clean-3rx   refs/pull/80/headr   r   checkoutr_   z!expected 1 batched checkout, got u    (개별 호출 회귀)z--   N)r   r   r   r5   rI   r   r    rR   r   r   rl   r'   )r   rp   expected_threer   r   rD   rE   rH   rW   r   pr_head_refr   pr_head_callsbatched_argss                 r   3test_create_clean_replacement_uses_batched_checkoutr   9  st    )+;<N 	< M 	<=L }
%C	\	"B"cRHUOFAq!,,N+7 - F
 f/000 &KIIqt9>d1g3Q;8N 	M  }" 
+C,>+??VW"
 !#L?d"""ab!"n444s   *D7c                   | j                  dd       t               t               t        dd      g}t        |      }t        |      \  }}}}|j	                  dt        t              d	      }t        |t              sJ |j                  d
k(  sJ |j                  dk(  sJ |j                  j                  d      t        t              k(  sJ y)uK   일괄 checkout 실패 시 ReplacementFailure.extra 에 paths(list) 박제.r=   rs   r_   zpathspec did not match any filer   r   rv   rw   rx   r   git_checkout_path_failedpathsNr   r   r   rI   r   r    rQ   rR   r   r   r   extrar   r   r   rD   rH   rW   r   s         r   Htest_create_clean_replacement_batched_checkout_failure_reports_all_pathsr   s  s     )+;<q!BCM
 }
%C"c2OFAq!--N+5 . G
 g1222==J&&&>>7777==W%n)====r   c                   | j                  dd       t               t               t               t               t               t        d      g}t        d      g}t        |      }t        |      }t	        |||      \  }}}}|j                  dt        t              d	      }t        |t              sJ |j                  |j                  z   D ]]  \  }}	|	j                  d
      dk(  sJ |	j                  d      dk(  sJ |	j                  d      dk(  sJ |	j                  d      dk(  r]J  |j                  |j                  z   D ]  \  }}	d|	v sJ d|	v rJ  y)u   env 에 GIT_AUTHOR_NAME/GIT_AUTHOR_EMAIL/GIT_COMMITTER_NAME/GIT_COMMITTER_EMAIL
    4개 키가 모두 주입되었는지 확인. default identity 검증.
    r=   rs   z	deadbeef
rt   z&https://github.com/owner/repo/pull/99
ru   rv   rw   rx   GIT_AUTHOR_NAMEzjeon-jonghyuk-taskctl-botGIT_AUTHOR_EMAILz2jeon-jonghyuk-taskctl-bot@users.noreply.github.comGIT_COMMITTER_NAMEGIT_COMMITTER_EMAILr|   r}   N)r   r   r   r5   rI   r   r    rQ   rR   r   r   r   )
r   rp   r   r   rD   rE   rH   rW   r   r*   s
             r   6test_create_clean_replacement_injects_bot_git_identityr     sz    )+;< 	< M 	<=L }
%C	\	"B"cRHUOFAq!,,N+5 - F
 f/000 ))bhh& f3ww()-HHHHww)*.bbbbww+,0KKKKww,-1eeee	f ))bhh& %3S   $$$%r   c           	     z   | j                  dd       t               t               t               t               t               t        d      g}t        d      g}t        |      }t        |      }d}d}|}t	        ||t               |d||      }	|	j                  d	t        t              d
      }
t        |
t              sJ |j                  |j                  z   D ]]  \  }}|j                  d      |k(  sJ |j                  d      |k(  sJ |j                  d      |k(  sJ |j                  d      |k(  r]J  y)un   생성자 인자로 커스텀 bot_git_name/bot_git_email 주입 시 env 에 그 값이 반영되는지 확인.r=   rs   z	cafebabe
rt   z'https://github.com/owner/repo/pull/100
zcustom-bot-namezcustom-bot@example.com)r?   r>   r@   rA   rB   bot_git_namebot_git_emailrv   rw   rx   r   r   r   r   N)r   r   r   r5   r   r7   r   r    rQ   rR   r   r   r   )r   rp   r   r   rD   rE   custom_namecustom_emailrG   rH   r   rW   r*   s                r   1test_create_clean_replacement_custom_bot_identityr     sQ   
 )+;< 	< M 	=>L }
%C	\	"B#K+LD #%( "F ,,N+5 - F
 f/000))bhh& >3ww()[888ww)*l:::ww+,;;;ww,-===	>r   c                h   | j                  dd       t               t               t               t               t               t        d      g}t        d      g}t        |      }t        |      }t	        |||      \  }}}}|j                  dt        t              d	      }t        |t              sJ d
}	t        |j                        D 
cg c]0  \  }
\  }}t        |      dk\  r|d   dk(  r|d   dk(  r
|d   |	k(  r|
2 }}}
}t        |      dk(  s
J d|        d}t        |j                        D 
cg c](  \  }
\  }}t        |      dk\  r|d   dk(  r
|d   |k(  r|
* }}}
}t        |      dk(  s
J d|        |d   |d   k  sJ d|d    d|d    d       yc c}}}
w c c}}}
w )ug   git_calls 시퀀스에서 fetch 호출이 checkout(PR head) 호출보다 먼저 실행되는지 확인.r=   rs   z	aabbccdd
rt   z'https://github.com/owner/repo/pull/101
ru   rv   rw   rx   z#refs/pull/80/head:refs/pull/80/headr   r   fetchr_   originr   u.   fetch 호출이 정확히 1회여야 함, got r   r   u9   checkout PR head 호출이 정확히 1회여야 함, got z
fetch(idx=u   ) 이 checkout(idx=u#   ) 보다 먼저 실행되어야 함N)r   r   r   r5   rI   r   r    rQ   rR   r   	enumerater   rl   )r   rp   r   r   rD   rE   rH   rW   r   	fetch_refir   fetch_indicesr   checkout_indicess                  r   ?test_create_clean_replacement_fetches_pull_head_before_checkoutr     s   
 )+;< 	< M 	=>L }
%C	\	"B"cRHUOFAq!,,N+5 - F
 f/000 6I'		2 a$t9>d1g0T!W5HTRSWXaMa 	
M  }"d&TUbTc$dd" &K'		2 a$t9>d1g3Q;8N 	
   A%u)bcsbt'uu% .q11 
]1%&&9:J1:M9NNqr1s   5F&=-F-c                x   | j                  dd       t               t        dd      g}t        |      }t        |      \  }}}}|j	                  dt        t              d	      }t        |t              sJ |j                  d
k(  sJ |j                  dk(  sJ |j                  j                  d      dk(  sJ y)u   fetch 단계 returncode != 0 시 ReplacementFailure(stage="fetch",
    reason="git_fetch_pull_head_failed", extra.ref 명시) 반환 박제.
    r=   rs   r_   zfatal: couldn't find remote refr   r   rv   rw   rx   r   git_fetch_pull_head_failedrefr   Nr   r   s         r   ?test_create_clean_replacement_fetch_failure_reports_stage_fetchr      s     )+;<q!BCM }
%C"c2OFAq!--N+5 . G
 g1222==G###>>9999==U#'::::r   )r   r   r   )r   intr   ro   r   ro   r,   r.   )r>   z_GitRecorder | Noner?   z_GhRecorder | Noner@   z_AuditRecorder | NonerA   zPath | NonerB   ro   r,   zEtuple[ReplacementPRRunner, _GitRecorder, _GhRecorder, _AuditRecorder]r<   )rp   r   r,   r-   )r   pytest.MonkeyPatchrp   r   r,   r-   )r   r   r,   r-   )0r2   
__future__r   r   syspathlibr   typingr   r   r   pytest__file__resolveparentsWORKSPACE_ROOTro   pathinsertanu_v2.merge_queue_executorr	   r
   r   anu_v2.replacement_pr_runnerr   r   r   r   r   r   r   r5   r7   rI   rQ   rY   r]   r`   rq   r   r   r   r   r   r   r   r   r   r3   r   r   <module>r      s   #  
  ) )  h'')11!4~chh&HHOOAs>*+ 
 e $P, P* * '+$(*."+"#" "" (	"
 " " K",	&& 87&46p,0`&>T65#6565 
65t>#>	>4*%#*%*% 
*%\->#->-> 
->b0#00 
0h;#;	;r   