
    <jZq                       d Z ddlm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	  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Zd	Zd
ZdZd!dZ	 	 	 d"	 	 	 	 	 	 	 d#dZeeeef	 	 	 	 	 	 	 	 	 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'd&dZ(d&dZ)d&dZ*d&dZ+d&dZ,d&d Z-y)'u[  회귀 테스트 17건 — anu_v2.worktree_cleanup (task-2550).

pytest 사용. 외부 부수효과(subprocess / file write / clock)는
모두 fake callable로 주입.

⚠️ 테스트 코드 내 "ghp_faketoken123abc" 등 raw token placeholder는
   실제 토큰이 아닌 테스트 fake 값임 — leak detector 오탐 방지를 위해 명시.
    )annotationsN)datetimetimezone)Path   )DEFAULT_CHAT_IDWorktreeCandidateWorktreeCleanup(aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa	task-2550ztask/task-2550-dev5-/home/jay/workspace/.worktrees/task-2550-dev5c            	     B    t        ddddddt        j                        S )u4   고정 시각 반환 — 테스트 결정론 보장.i           r   )tzinfo)r   r   utc     *anu_v2/tests/test_worktree_cleanup_2550.py_fake_clockr   $   s    D!RQ(,,??r   c                4    t        j                  g | ||      S )N)args
returncodestdoutstderr)
subprocessCompletedProcess)r   r   r   s      r   _make_completed_processr   )   s#    
 &&	 r   c                     t        | |||      S )Npathbranchtask_idhead_sha)r	   r!   s       r   _make_candidater&   6   s     $vwQYZZr   c                     ~ ~t        d      S )ue   기본 fake subprocess runner — rc=0, no output. unused parameter는 명시적으로 del로 처리.r   r   )_args_kwargss     r   _noop_runnerr+   ?   s    w"1%%r   c                J   | dz  dz  }|j                  dd       |t         dz  j                          t        t        t
        |       }|j                  t              }|j                  dk(  sJ |j                  dk(  sJ |j                  du sJ |j                  d	k(  sJ y
)u*   .done.acked 마커 존재 → passed=True.memoryeventsTparentsexist_ok.done.ackedsubprocess_runnerclockworkspace_root   
done_ackedokN)mkdirFAKE_TASK_IDtouchr
   r+   r   check_safety_1_done_acked	conditionnamepasseddetailtmp_path
events_dircleanupresults       r   test_safety_1_done_acked_passrG   H   s     H$x/JTD1\N+..557&G
 ..|<Fq   ;;,&&&==D   ==D   r   c                    t        t        t        |       }|j                  t              }|j
                  dk(  sJ |j                  dk(  sJ |j                  du sJ d|j                  v sJ y)u   마커 없음 → passed=False.r3   r7   r8   FmissingN)	r
   r+   r   r=   r;   r>   r?   r@   rA   )rC   rE   rF   s      r   test_safety_1_done_acked_failrJ   _   sq    &G
 ..|<Fq   ;;,&&&==E!!!%%%r   c                   t        j                  ddt        dg      fd}t        |t        |       }|j                  t              }|j                  dk(  sJ |j                  dk(  sJ |j                  du sJ d|j                  v sJ y	)
u9   gh API mock으로 PR state=MERGED 반환 → passed=True.e   MERGEDnumberstateheadRefNamec                H    d| v rd| v rt        dd      S t        ddd      S Nprlistr    r(   r   _pr_responses     r   fake_runnerz1test_safety_2_pr_merged_pass.<locals>.fake_runnerw   .    4<FdN*1k2>>&q"b11r   r3   r   	pr_mergedTNjsondumpsFAKE_BRANCHr
   r   check_safety_2_pr_mergedr;   r>   r?   r@   rA   rC   rZ   rE   rF   rY   s       @r   test_safety_2_pr_merged_passrc   q   s    **+F K2
 %G
 --l;Fq   ;;+%%%==D   v}}$$$r   c                   t        j                  ddt        dg      fd}t        |t        |       }|j                  t              }|j                  dk(  sJ |j                  dk(  sJ |j                  du sJ d|j                  v sJ y	)
u8   gh API mock으로 PR state=OPEN 반환 → passed=False.f   OPENrN   c                H    d| v rd| v rt        dd      S t        ddd      S rS   r(   rW   s     r   rZ   z6test_safety_2_pr_merged_fail_open.<locals>.fake_runner   r[   r   r3   r   r\   FNr]   rb   s       @r   !test_safety_2_pr_merged_fail_openrh      s    **D K2
 %G
 --l;Fq   ;;+%%%==E!!!V]]"""r   c                J   | dz  dz  }|j                  dd       |t         dz  j                          t        t        t
        |       }|j                  t              }|j                  dk(  sJ |j                  dk(  sJ |j                  du sJ |j                  d	k(  sJ y
)u*   .merge-done 마커 존재 → passed=True.r-   r.   Tr/   .merge-doner3      
merge_doner9   N)r:   r;   r<   r
   r+   r   check_safety_3_merge_doner>   r?   r@   rA   rB   s       r   test_safety_3_merge_done_passrn      s    H$x/JTD1\N+..557&G
 ..|<Fq   ;;,&&&==D   ==D   r   c                    d }t        |t        |       }|j                  t              }|j                  dk(  sJ |j
                  dk(  sJ |j                  du sJ d|j                  v sJ y)u2   git merge-base --is-ancestor rc=0 → passed=True.c                F    d| v rd| v rt        ddd      S t        ddd      S )N
merge-base--is-ancestorr   rV   r(   r   rX   s     r   rZ   z6test_safety_4_branch_in_main_pass.<locals>.fake_runner   /    4Ot$;*1b"55&q"b11r   r3      branch_in_mainTancestorNr
   r   check_safety_4_branch_in_mainr`   r>   r?   r@   rA   rC   rZ   rE   rF   s       r   !test_safety_4_branch_in_main_passr{      sy    2
 %G
 22;?Fq   ;;****==D   &&&r   c                    d }t        |t        |       }|j                  t              }|j                  dk(  sJ |j
                  dk(  sJ |j                  du sJ d|j                  v sd|j                  v sJ yy)	u3   git merge-base --is-ancestor rc=1 → passed=False.c                F    d| v rd| v rt        ddd      S t        ddd      S )Nrq   rr   r7   rV   r   r(   rs   s     r   rZ   z6test_safety_4_branch_in_main_fail.<locals>.fake_runner   rt   r   r3   ru   rv   FzNOT ancestorzrc=1Nrx   rz   s       r   !test_safety_4_branch_in_main_failr~      s    2
 %G
 22;?Fq   ;;****==E!!!V]]*f.EEE.E*r   c                    d }t        |t        |       }|j                  t              }|j                  dk(  sJ |j
                  dk(  sJ |j                  du sJ d|j                  v sJ y)u+   pgrep rc=1 (매치 없음) → passed=True.c                >    d| v rt        ddd      S t        ddd      S )Npgrepr7   rV   r   r(   rs   s     r   rZ   z2test_safety_5_not_in_use_pass.<locals>.fake_runner   s'    d?*1b"55&q"b11r   r3   r   
not_in_useTz
no processNr
   r   check_safety_5_not_in_useFAKE_WORKTREE_PATHr>   r?   r@   rA   rz   s       r   test_safety_5_not_in_use_passr      sy    2
 %G
 ../ABFq   ;;,&&&==D   6==(((r   c                    d }t        |t        |       }|j                  t              }|j                  dk(  sJ |j
                  dk(  sJ |j                  du sJ d|j                  v sJ y)u,   pgrep rc=0 (매치 있음) → passed=False.c                >    d| v rt        ddd      S t        ddd      S )Nr   r   z12345
rV   r(   rs   s     r   rZ   z2test_safety_5_not_in_use_fail.<locals>.fake_runner  s'    d?*1i<<&q"b11r   r3   r   r   FprocessNr   rz   s       r   test_safety_5_not_in_use_failr     sy    2
 %G
 ../ABFq   ;;,&&&==E!!!%%%r   c                \   t        t        t        |       }|j                  d      }|j                  dk(  sJ |j
                  dk(  sJ |j                  du sJ |j                  d      }|j                  dk(  sJ |j
                  dk(  sJ |j                  du sJ d|j                  v sJ y)u9   apply=True → passed=True; apply=False → passed=False.r3   T   apply_explicitFzdry-runN)r
   r+   r   check_safety_6_apply_explicitr>   r?   r@   rA   )rC   rE   result_trueresult_falses       r   test_safety_6_apply_explicitr     s    &G 77=K  A%%%////%%%88?L!!Q&&& 0000%'''+++++r   c                L   d }t        |t        |       }t        t        | dz              }|j	                  |d      }|j
                  du sJ |j                  du sJ |j                  du sJ |j                  J d	|j                  j                         v sJ | d
z  dz  }t        |j                  d            }t        |      dk\  sJ d       t        j                  |d   j                  d            }|d   t         k(  sJ dt          d       |d   t"        k(  sJ y)ut   dirty=True인 worktree → CleanupResult.skipped=True, skip_reason 포함 "dirty", applied=False, log 파일 생성.c                F    d| v rd| v rt        ddd      S t        ddd      S )Nstatus--porcelainr   zM  modified_file.py
rV   r(   rs   s     r   rZ   z0test_dirty_worktree_skipped.<locals>.fake_runner7  s0    t 5*1.ErJJ&q"b11r   r3   some-worktreer"   FapplyTNdirtyr-   r.   zworktree-cleanup-skipped-*.jsonr7   u0   dirty skip 시 log 파일이 생성되어야 함r   zutf-8)encodingchat_idu   chat_id가 u   이어야 함r$   )r
   r   r&   strcleanup_worktreer   skippedappliedskip_reasonlowerrU   globlenr^   loads	read_textr   r;   )rC   rZ   rE   	candidaterF   rD   	log_fileslog_datas           r   test_dirty_worktree_skippedr   4  sA   2 %G
  SO)C%DEI%%iu%=F<<4>>T!!!>>U""")))f((..0000 H$x/JZ__%FGHIy>QR RR zz)A,00'0BCHI/1_[@QQ^3__1I,...r   c                8   t        t        t        |       }t        t	        |             }|j                  |d      }|j                  du sJ |j                  du sJ |j                  du sJ |j                  J d|j                  j                         v sJ y)ue   workspace_root와 동일 path → is_main=True, applied=False, skip_reason에 'main worktree' 포함.r3   r   Tr   FNzmain worktree)r
   r+   r   r&   r   r   is_mainr   r   r   r   )rC   rE   r   rF   s       r    test_main_worktree_never_deletedr   [  s    &G  S]3I%%it%<F>>T!!!>>U""">>T!!!)))f00668888r   c                z     dz  dz  }|j                  dd       |t         dz  j                          |t         dz  j                          t        j                  ddt
        d	g       fd
}t        |t               }|j                  d      }t        d |D              }|dk(  sJ d| d       y)u@   apply=False, 모든 safety PASS여도 applied_count=0 어설션.r-   r.   Tr/   r2   rj   g   rM   rN   c                $   d| v rd| v rt        ddd      S d| v rd| v rt        dd      S d| v rt        ddd      S d| v rt        d	dd      S d
| v r3d| v r/t        dz        }d| dt         dt         d}t        d|d      S t        ddd      S )Nr   r   r   rV   rT   rU   rq   r   r7   worktreer   	worktree 
HEAD z
branch refs/heads/
)r   r   FAKE_SHAr`   )r   rX   wt_path	porcelainrY   rC   s       r   rZ   z4test_dry_run_zero_actual_delete.<locals>.fake_runner~  s    t 5*1b"554<FdN*1k2>>4*1b"55d?*1b"55&D.(_45GG9 % z "%%0M5 
 +1i<<&q"b11r   r3   Fr   c              3  :   K   | ]  }|j                   sd   yw)r7   N)r   ).0rs     r   	<genexpr>z2test_dry_run_zero_actual_delete.<locals>.<genexpr>  s     8aaii8s   r   u3   dry-run에서 applied_count가 0이어야 함 (got )N)
r:   r;   r<   r^   r_   r`   r
   r   cleanup_all_dry_runsum)rC   rD   rZ   rE   resultsapplied_countrY   s   `     @r   test_dry_run_zero_actual_deleter   r  s     H$x/JTD1\N+..557\N+..557**+F K2( %G )))6G8788M Ae!TUbTccdeer   c                   | dz  dz  }|j                  dd       |t         dz  j                          |t         dz  j                          t        j                  ddt
        d	g      d
difd}t        |t        |       }t        t        | dz              }|j                  |d      }|j                  du sJ |j                  du sJ |j                  du sJ d
   du sJ d       y)ua   apply=True, 6대 safety 모두 PASS, mock subprocess 'git worktree remove' rc=0 → applied=True.r-   r.   Tr/   r2   rj   h   rM   rN   calledFc                    d| v rd| v rt        ddd      S d| v rd| v rt        dd      S d| v rt        ddd      S d| v rt        d	dd      S d
| v rd| v rdd<   t        ddd      S t        ddd      S )Nr   r   r   rV   rT   rU   rq   r   r7   r   removeTr   r(   )r   rX   rY   removeds     r   rZ   z:test_apply_mode_with_all_safe_deletes.<locals>.fake_runner  s    t 5*1b"554<FdN*1k2>>4*1b"55d?*1b"55(d"2 $GH*1b"55&q"b11r   r3   r   r   r   u*   git worktree remove가 호출되어야 함N)r:   r;   r<   r^   r_   r`   r
   r   r&   r   r   all_safer   r   )rC   rD   rZ   rE   r   rF   rY   r   s         @@r   %test_apply_mode_with_all_safe_deletesr     s   H$x/JTD1\N+..557\N+..557**+F K G2 %G  SO)C%DEI%%it%<F??d""">>T!!!>>U"""8$R&RR$r   c                V   | dz  dz  }|j                  dd       |t         dz  j                          |t         dz  j                          t        j                  ddt
        d	g      fd
}t        |t        |       }t        t        | dz              }|j                  |d      }|j                  du sJ |j                  du sJ |j                  du sJ |j                  J |j                  D cg c]  }|j                   r|j"                   }}d|v sJ yc c}w )uY   safety_2 FAIL (PR OPEN) → cleanup_worktree all_safe=False, skipped=True, applied=False.r-   r.   Tr/   r2   rj   i   rf   rN   c                    d| v rd| v rt        ddd      S d| v rd| v rt        dd      S d| v rt        ddd      S d| v rt        d	dd      S t        ddd      S )
Nr   r   r   rV   rT   rU   rq   r   r7   r(   rW   s     r   rZ   z-test_unmerged_pr_skipped.<locals>.fake_runner  sy    t 5*1b"554<FdN*1k2>>4*1b"55d?*1b"55&q"b11r   r3   r   r   r   FNr\   )r:   r;   r<   r^   r_   r`   r
   r   r&   r   r   r   r   r   r   safety_resultsr@   r?   )	rC   rD   rZ   rE   r   rF   r   failed_safety_namesrY   s	           @r   test_unmerged_pr_skippedr     s8   H$x/JTD1\N+..557\N+..557**D K	2 %G  SO)C%DEI%%it%<F??e###>>T!!!>>U""")))+1+@+@Qa166QQ---- Rs   =D&D&c                   d}d| | dz  dz  }|j                  dd       |t         dz  j                          |t         dz  j                          t        j                  d	d
t
        dg      fd}t        |t        |       }t        t        | dz              }|j                  |d      }|j                  J ||j                  vsJ d|j                          d|j                  v sJ d|j                          y)u   subprocess error 시 raw token 'ghp_faketoken123abc'가 stderr에 있어도 skip_reason에는 ***MASKED***로 출력.

    ⚠️ 'ghp_faketoken123abc'는 테스트 fake 값 (실제 토큰 아님).
    ghp_faketoken123abcz(error: authentication failed with token r-   r.   Tr/   r2   rj   j   rM   rN   c                    d| v rd| v rt        ddd      S d| v rd| v rt        dd      S d| v rt        ddd      S d| v rt        d	dd      S d
| v rd| v rt        d	d      S t        ddd      S )Nr   r   r   rV   rT   rU   rq   r   r7   r   r   r(   )r   rX   fake_stderr_with_tokenrY   s     r   rZ   z9test_token_not_leaked_in_skip_reason.<locals>.fake_runner  s    t 5*1b"554<FdN*1k2>>4*1b"55d?*1b"55(d"2*1b2HII&q"b11r   r3   r   r   r   Nu'   skip_reason에 raw token이 노출됨: z***MASKED***u.   skip_reason에 ***MASKED*** 처리가 없음: )r:   r;   r<   r^   r_   r`   r
   r   r&   r   r   r   )	rC   fake_raw_tokenrD   rZ   rE   r   rF   r   rY   s	          @@r   $test_token_not_leaked_in_skip_reasonr     s:    +NGGWXH$x/JTD1\N+..557\N+..557**+F K2 %G  SO)C%DEI%%it%<F )))!3!33 
1&2D2D1EF3 V/// 
89K9K8LM/r   c                F   d}d}d}d| dt          d| dd d| dd	 d
fd}t        |t        |       }|j                         }t	        |      dk(  sJ dt	        |       d       |D ch c]  }|j
                   }}||v sJ ||v sJ ||v sJ |D ci c]  }|j
                  |j                   }	}|	|   dk(  sJ |	|   dk(  sJ |D ci c]  }|j
                  |j                   }
}|
|   t         k(  sJ |
|   dk(  sJ |
|   d	k(  sJ yc c}w c c}w c c}w )u9   git worktree list --porcelain mock output parsing 정상.z/home/jay/workspacer   z-/home/jay/workspace/.worktrees/task-2545-dev3r   r   z"
branch refs/heads/main

worktree (bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbz1
branch refs/heads/task/task-2550-dev5

worktree (ccccccccccccccccccccccccccccccccccccccccz'
branch refs/heads/task/task-2545-dev3
c                P    d| v rd| v rd| v rt        dd      S t        ddd      S )Nr   rU   r   r   rV   r(   )r   rX   porcelain_outputs     r   rZ   zBtest_enumerate_parses_worktree_list_porcelain.<locals>.fake_runnerE  s7    &D.]d5J*1.>CC&q"b11r   r3   rk   u*   3개 worktree가 파싱되어야 함 (got r   r   z	task-2545N)r   r
   r   enumerate_worktreesr   r"   r$   r%   )rC   	wt_path_1	wt_path_2	wt_path_3rZ   rE   
candidatescpathstask_mapsha_mapr   s              @r   -test_enumerate_parses_worktree_list_porcelainr   1  s   %I?I?I I; z  ; z  ; z 2
	3 2
 %G ,,.Jz?a`#McR\oM^^_!`` ((QVV(E( ,66a		!6H6I+---I+--- ,66aqvvqzz!6G69)))9)))9))) ) 7
 7s   .DDD)returnr   )r   rV   rV   )r   intr   r   r   r   r   subprocess.CompletedProcess)
r"   r   r#   r   r$   z
str | Noner%   r   r   r	   )r)   objectr*   r   r   r   )rC   r   r   None).__doc__
__future__r   r^   r   sysr   r   pathlibr   __file__resolver0   WORKSPACE_ROOTr   r"   insertanu_v2.worktree_cleanupr   r	   r
   r   r;   r`   r   r   r   r&   r+   rG   rJ   rc   rh   rn   r{   r~   r   r   r   r   r   r   r   r   r   r   r   r   r   <module>r      si   #   
 '  h'')11!4~chh&HHOOAs>*+  #D @ 


 
 !	
 #&	[
[[ [ 	[
 [&!.&$%6#6!,'0F0)0&0,.!/N9.*f`(S\%.V0l2*r   