
    iJ                    Z   d Z ddlmZ ddlZddlZddlZddlmZ ddlm	Z	 ddl
mZ ddlZ ee      j                         j                  d   Zedz  Zej$                  j'                  d ee             ej$                  j'                  d ee             ddlZdd	lmZmZ d
Zddddd	 	 	 	 	 	 	 	 	 	 	 d-dZd.dZ G d d      Zd/dZd/dZd0dZ d0dZ!d0dZ"d1dZ#d0dZ$d0dZ%d0dZ&d0dZ'd0dZ(d0dZ)d0d Z*d0d!Z+d0d"Z,d2d#Z-d3d$Z.d0d%Z/d0d&Z0d0d'Z1d0d(Z2d0d)Z3d0d*Z4d4d+Z5d0d,Z6y)5u  Regression tests for ``scripts/auto_merge_controller.py`` (task-2444).

Coverage:

* All 8 required-check enforcement
* mergeable_state BLOCKED / behind / dirty / unstable rejection
* gemini-review-gate failure / SKIPPED rejection (with label)
* cancelled marker → close (no merge attempt)
* Forbidden flag tripwire (--admin)
* Forbidden direct push to main
* End-to-end 6 scenarios (A1..A6) using mocked GitHub API responses.

Tests are pure-Python — no network, no real ``gh`` CLI invocations.
    )annotationsN)Path)Any)mock   scripts)FileLockLockTimeoutzTestOwner/test-repoztask/task-9999-dev2main(deadbeefdeadbeefdeadbeefdeadbeefdeadbeefclean)branchbaseshamergeable_statec                    | ||dd|i|dd d dS )N)refr   r   F)numberheadr   r   merged	merged_atmerge_commit_sha )r   r   r   r   r   s        Y/home/jay/workspace/.worktrees/task-2444-dev2/tests/scripts/test_auto_merge_controller.pymake_prr   ,   s,     s+*      c                     t         j                  D cg c]  }|dd	 }}t        |       }|D ]  }|d   |v s||d      |d<    |S c c}w )z;Return 8 required check-runs all ``success`` (overridable).successname
conclusionr    r!   )amcREQUIRED_CHECKSdict)extra_statusr    runs	overridesrs        r   all_success_check_runsr)   ?   sd    @C@S@STT3TDT\"I 3V9	!'&	2AlO3 K Us   Ac                  N    e Zd ZdZddddd	 	 	 	 	 	 	 	 	 d	dZd
dZddZddZy)
FakeGitHubzDIn-memory stub for the gh REST + GraphQL surface used by controller.N(0000000000000000000000000000000000000000)
check_runspr_full_overridesreview_threads	main_headc                   t        |      | _        |xs i | _        |xs i | _        |xs i | _        |g| _        g | _        y N)listprsr-   r.   r/   main_head_historycalls)selfr4   r-   r.   r/   r0   s         r   __init__zFakeGitHub.__init__L   sF     9$*!2!8b,2"+,.
r   c                   | j                   j                  d|f       |j                  d      rdd| j                  d   iiS d|v rD|j	                  d      d   j	                  d	      d
   }d| j
                  j                  |g       iS d|v r|j                  d      sot        |j                  d	d      d         t        fd| j                  D        d       }|t        d       | j                  j                  i       }i ||S |j                  d      r&| j                  D cg c]  }|d   d   dk(  s| c}S t        d|       c c}w )Napiz/branches/maincommitr   z/check-runsz	/commits/   /r   r-   z/pulls/z?state=open&base=mainc              3  4   K   | ]  }|d    k(  s|  yw)r   Nr   ).0pnums     r   	<genexpr>z!FakeGitHub.api.<locals>.<genexpr>f   s     Cq(s0BCs   zPR not found: r   r   r   zunexpected path: )r6   appendendswithr5   splitr-   getintrsplitnextr4   RuntimeErrorr.   AssertionError)r7   pathr   r   overriderA   rB   s         @r   r:   zFakeGitHub.api]   sV   

5$-(==)*ud&<&<R&@ABBD **[)!,2237:C $//"5"5c2">??T]]3J%Kdkk#q)"-.CCDHHCTJD|"^C5#9::--11#r:H'd'h''==01#xxF!1V9U+;v+EAFF0788 Gs   .E?Ec                    |d   }| j                   j                  |g       D cg c]  }d|i }}ddddd|iiiiiS c c}w )Nr   
isResolveddata
repositorypullRequestreviewThreadsnodes)r/   rG   )r7   _query	variablesrB   resolvedrU   s         r   graphqlzFakeGitHub.graphqlo   se    !:>:M:M:Q:QRUWY:Z[h,)[[}%@P.QR
 	
 \s   =c                :    | j                   j                  |       y r2   )r5   rD   )r7   new_shas     r   advance_mainzFakeGitHub.advance_mainy   s    %%g.r   )
r4   list[dict[str, Any]]r-   z&dict[str, list[dict[str, Any]]] | Noner.   z dict[int, dict[str, Any]] | Noner/   zdict[int, list[bool]] | Noner0   str)rM   r^   returnr   )rV   r^   rW   dict[str, Any]r_   r   )r[   r^   r_   None)__name__
__module____qualname____doc__r8   r:   rY   r\   r   r   r   r+   r+   I   sZ    N >B>B7;!/ "/ ;	/
 </ 5/ /"9$
/r   r+   c                    | dz  }t        |d      5  	 d d d        t        |d      5  	 d d d        y # 1 sw Y   !xY w# 1 sw Y   y xY w)Nx.lockr=   timeout)r	   )tmp_pathrA   s     r   test_filelock_releases_on_exitrk      sW    8A	!Q	  
!Q	    s   5A>A
c                6   | dz  }t        |d      }|j                          	 t        j                  t              5  t        |d      5  	 d d d        d d d        |j                          y # 1 sw Y   "xY w# 1 sw Y   &xY w# |j                          w xY w)Nrg   
   rh   g333333?)r	   acquirepytestraisesr
   release)rj   rA   holders      r   &test_filelock_blocks_concurrent_holderrs      s    8Aa$F
NN]];' 	!S) 	 	 	 	 	s:   B A:A.A:B .A7	3A::B?B Bc                     t        j                  t        d      5  t        j                  g d       d d d        y # 1 sw Y   y xY w)NzFORBIDDEN.*--adminmatch)ghprmerge1--admin--mergero   rp   rK   r"   run_cmdr   r   r   test_run_cmd_blocks_admin_flagr      s7    	|+@	A FDEF F F	   =Ac                     t        j                  t        d      5  t        j                  g d       d d d        y # 1 sw Y   y xY w)NFORBIDDEN.*push to mainru   )gitpushoriginr   r}   r   r   r   $test_run_cmd_blocks_direct_main_pushr      s3    	|+E	F 7567 7 7r   c                     t        j                  t        d      5  t        j                  g d       d d d        y # 1 sw Y   y xY w)Nr   ru   )r   r   r   z	HEAD:mainr}   r   r   r   %test_run_cmd_blocks_head_to_main_pushr      s3    	|+E	F <:;< < <r   c                    i fd}| j                  t        j                  d|       t        j                  g d      }|j                  dk(  sJ dd   vsJ y )Nc               >    | d<   t        j                  | ddd      S )Ncmdr   ok )
subprocessCompletedProcess)r   capture_outputtextcheckcaptureds       r   fake_runz8test_run_cmd_allows_normal_gh_pr_merge.<locals>.fake_run   s#    **34<<r   run)rw   rx   ry   5z--autor|   z--delete-branchr   r{   r   )setattrr"   r   r~   
returncode)monkeypatchr   procr   s      @r   &test_run_cmd_allows_normal_gh_pr_merger      sX    !H= x8;;YZD??aHUO+++r   c                     t        dd      } t        | g      }t        j                  | t        |j
                  |j                  d       }|J d|j                  v sJ y )Nr=   develop)r   r4   c                     yNFr   _ts    r   <lambda>z3test_evaluate_skips_non_main_base.<locals>.<lambda>       r   repor:   rY   cancelled_marker_existsznot main)r   r+   r"   evaluate_prREPOr:   rY   reasonrx   fakedecisions      r   !test_evaluate_skips_non_main_baser      s\    		#B2$D
488T\\ 0H (((r   c                     t        dd      } t        | g| d   d   t               i      }t        j                  | t
        |j                  |j                  d       }|J d	|j                  v sJ y )
Nr=   ztask/task-1234-dev1r   r   r   r4   r-   c                    | dk(  S )Nz	task-1234r   )tids    r   r   z6test_evaluate_skips_cancelled_marker.<locals>.<lambda>   s    C;,> r   r   	cancelled	r   r+   r)   r"   r   r   r:   rY   r   r   s      r   $test_evaluate_skips_cancelled_markerr      sw    	0	1BDvJu%'='?@D 
488T\\ >H (//)))r   c                 :   t        d      } t               D cg c]  }|d   dk7  s| }}t        | g| d   d   |i      }t        j                  | t
        |j                  |j                  d       }|J d|j                  v sJ |j                  J y c c}w )	Nr=   r    qc-checkr   r   r   c                     yr   r   r   s    r   r   z<test_evaluate_skips_missing_required_check.<locals>.<lambda>   r   r   r   
r   r)   r+   r"   r   r   r:   rY   r   label)rx   r(   r&   r   r   s        r   *test_evaluate_skips_missing_required_checkr      s    	B-/K!1V9
3JAKDK2$BvJu,=t+DED
488T\\ 0H (((>>!!! Ls
   BBc                    t        d      } t        d      }t        | g| d   d   |i      }t        j                  | t
        |j                  |j                  d       }|J d|j                  v sJ |j                  d	k(  sJ y )
Nr=   )r   failurer   r   r   c                     yr   r   r   s    r   r   z=test_evaluate_skips_failed_check_with_label.<locals>.<lambda>   r   r   r   r   auto-merge-blockedr   rx   r&   r   r   s       r   +test_evaluate_skips_failed_check_with_labelr      s    	B!"9:D2$BvJu,=t+DED
488T\\ 0H (((>>1111r   c                 
   t        dd      } t        | g| d   d   t               i      }t        j                  | t
        |j                  |j                  d       }|J d|j                  v sJ |j                  d	k(  sJ y )
Nr=   blockedr   r   r   r   c                     yr   r   r   s    r   r   z=test_evaluate_skips_blocked_mergeable_state.<locals>.<lambda>   r   r   r   r   )
r   r+   r)   r"   r   r   r:   rY   r   r   r   s      r   +test_evaluate_skips_blocked_mergeable_stater      s    	I	.BDvJu%'='?@D 
488T\\ 0H '''>>1111r   c                     t        dd      } t        | g| d   d   t               i      }t        j                  | t
        |j                  |j                  d       }|J d|j                  v sJ y )	Nr=   behindr   r   r   r   c                     yr   r   r   s    r   r   z1test_evaluate_skips_behind_main.<locals>.<lambda>  r   r   r   r   r   s      r   test_evaluate_skips_behind_mainr      sv    	H	-BDvJu%'='?@D 
488T\\ 0H x&&&r   c                     t        d      } t        | g| d   d   t               idddgi      }t        j                  | t
        |j                  |j                  d       }|J d	|j                  v sJ y )
Nr=   r   r   TFr4   r-   r/   c                     yr   r   r   s    r   r   z8test_evaluate_skips_unresolved_threads.<locals>.<lambda>  r   r   r   
unresolvedr   r   s      r   &test_evaluate_skips_unresolved_threadsr     s    	BDvJu%'='?@D%=)D
 
488T\\ 0H 8??***r   c                     t        d      } t        | g| d   d   t               idddgi      }t        j                  | t
        |j                  |j                  d       }|J y )Nr=   r   r   Tr   c                     yr   r   r   s    r   r   z5test_evaluate_passes_when_all_clear.<locals>.<lambda>%  r   r   r   )r   r+   r)   r"   r   r   r:   rY   r   s      r   #test_evaluate_passes_when_all_clearr     sm    	BDvJu%'='?@D$<(D
 
488T\\ 0H r   c                     t        d      } t        d      }t        | g| d   d   |i      }t        j                  | t
        |j                  |j                  d       }|J |j                  d	k(  sJ y)
zIgemini-review-gate==SKIPPED should be rejected with gemini-blocked label.r=   gemini-review-gateskippedr   r   r   c                     yr   r   r   s    r   r   zMtest_evaluate_skipped_check_without_success_flag_is_blocked.<locals>.<lambda>1  r   r   r   Ngemini-blocked)	r   r)   r+   r"   r   r   r:   rY   r   r   s       r   ;test_evaluate_skipped_check_without_success_flag_is_blockedr   *  su    	B!"CDD2$BvJu,=t+DED
488T\\ 0H >>----r   c                    	
 g g 
g g 	dd	 fd}d
 
fd} fd}d fd}d	fd} fd}
	||||||d
S )zDBuild a process_open_prs harness using FakeGitHub side-effect stubs.c                x    j                   d   }| |d}|r|j                  |       j                  |       |S )Nr<   )stager0   )r5   updaterD   )r   extrar   entryauditr   s       r   head_recorderz)_make_cycle_runner.<locals>.head_recorderC  s=    $$R(*/c BLLU
r   c                    j                  |        d| dj                  dd      }j                  |       j                  D ]  }|d   | k(  sd|d<   d|  d	|d
<   ||d<     t	        j
                  g ddd      S )Nzmerge-08x(   0r   Tr   2026-05-04T00:00:0Zr   r   r   r   )argsr   stdoutstderr)rD   ljustr\   r4   r   r   )pr_num_repor[   rA   r   merged_callss       r   safe_merge_fnz)_make_cycle_runner.<locals>.safe_merge_fnK  s    F#6#,'--b#6'" 	0A{f$"(#5fXQ!?+(/$%		0
 **qTVWWr   c                f    j                   D cg c]  }|d   r	|d   d   dk(  s| c}S c c}w )Nr   r   r   r   r   )r   rA   r   s     r   list_prsz$_make_cycle_runner.<locals>.list_prsW  s1    88Va1X;1V9U;Kv;UVVVs   
...c                P    | j                   j                  dt                     v S )N_cancelled_set)__dict__rG   set)r   r   s    r   cancelled_markerz,_make_cycle_runner.<locals>.cancelled_markerZ  s"    dmm''(8#%@@@r   c                ,    j                  | |f       y r2   )rD   )r   r   r   labelss      r   label_blockedz)_make_cycle_runner.<locals>.label_blocked]  s    vuo&r   c                |    j                  | d          j                  D ]  }|d   | d   k(  sd|d<   d|d<    y )Nr   closedstateFr   )rD   r4   )rx   r   rA   closed_callsr   s      r   handle_cancelledz,_make_cycle_runner.<locals>.handle_cancelled`  sJ    BxL) 	$A{bl*%'
#(	$r   )
r   r   r   r   r   r   r   r   r   r   r2   )r   r^   r   zdict[str, Any] | None)r   rH   r   r^   )r   r^   r_   bool)r   rH   r   r^   r   r^   r_   ra   r   )r   r   r   r   r   r   r   r   r   r   r   s   `      @@@@r   _make_cycle_runnerr  <  sb    "$E L L$&F
XWA'$ $$&&,&, r   c                     t         j                  j                  t        d|d         5  t         j                  j                  t        d|d         5  t         j                  j                  t        d fd      5  t        j                  t
         j                   j                  |d   |d   |d   |d	   d
       cddd       cddd       cddd       S # 1 sw Y   nxY wddd       n# 1 sw Y   nxY wddd       y# 1 sw Y   yxY w)zFInvoke process_open_prs with full dependency injection + side effects.r   handle_cancelled_prr   
post_checkc                4    dd|  ddj                   d   dS )NTr   r   r<   )r   r   branch_deletedr   )r5   )pnbrr   r   s      r   r   z%_run_with_overrides.<locals>.<lambda>y  s+    ,>rd!*D#9O9OPR9SD r   r   r   r   r   c                      y)Ng    mAr   r   r   r   r   z%_run_with_overrides.<locals>.<lambda>  r   r   )r   r:   rY   r   r   r   r   nowN)r   patchobjectr"   process_open_prsr   r:   rY   )r   harnesss   ` r   _run_with_overridesr  u  s    			31I	J 
			3 5w?Q7R	S
			3 / 

 ##LLZ(!/2!/2$+,>$?$	

 
 
 
 
 
 
 
 
 
 
sB   )D)C/<AC>	C/	DC#C/&	D/C8	4DDc                    t        d      } t        | g| d   d   t               iddgid      }t        |      }t	        ||      }|d   dgk(  sJ t        |j                        dk(  sJ |j                  d	   j                  dk(  sJ |j                  d	   j                  du sJ |j                  d	   j                  |j                  d	   j                  k7  sJ |d
   D cg c]  }|d   	 }}d|v sJ d|v sJ d|v sJ y c c}w )Ne   r   r   T(aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaar4   r-   r/   r0   r   r=   r   r   r   zbefore-cyclezbefore-merge-pr-101zafter-merge-pr-101)
r   r+   r)   r  r  lenr   	pr_numbermain_head_beforemain_head_after)rx   r   r  resultastagess         r   &test_A1_all_checks_success_auto_mergesr    s0   	BDvJu%'='?@dV}"	D !&G w/F>"se+++v}}"""==%%,,,==""d***==,,a0@0P0PPPP")'"23Qaj3F3V### F***6))) 4s   "Dc                 .   t        d      } t        d      }t        | g| d   d   |idddiidg i      }t        |      }t	        ||      }|d   g k(  sJ t        d	 |j                  D              sJ d
|d   D cg c]  \  }}|	 c}}v sJ y c c}}w )Nf   )cancel-kill-switchr   r   r   r   r   )r4   r-   r.   r/   r   c              3  8   K   | ]  }d |j                   v   yw)r  Nr   r@   ss     r   rC   z=test_A2_ci_failure_blocks_merge_with_label.<locals>.<genexpr>       HA#qxx/H   r   r   r   r)   r+   r  r  anyr   rx   r&   r   r  r  _lbls          r   *test_A2_ci_failure_blocks_merge_with_labelr*    s    	B!"CDDDvJu%t,!2I >?Ry	D !&G w/F>"b(((HHHHH wx7H$IVQS$IIJI$Is   =Bc                 $   t        d      } t               D cg c]  }|d   dv r| }}t        | g| d   d   |idg i      }t        |      }t	        ||      }|d   g k(  sJ |d   g k(  sJ t        d	 |j                  D              sJ y c c}w )
Ng   r    >   hidden-path-auditguardci/guardr   r  r   r   r   r   r   c              3  8   K   | ]  }d |j                   v   yw)missingNr   r!  s     r   rC   z7test_A3_pending_checks_skip_no_label.<locals>.<genexpr>  s     =yAHH$=r$  r%  )rx   r(   r&   r   r  r  s         r   $test_A3_pending_checks_skip_no_labelr2    s    	B-/ j!yhh  jD jDvJu%t,RyD
 !&G w/F>"b(((8"""=fnn====js   Bc                 $   t        d      } t        d      }t        | g| d   d   |idg i      }t        |      }t	        ||      }|d   g k(  sJ d|d   D cg c]  \  }}|	 c}}v sJ t        d	 |j                  D              sJ y c c}}w )
Nh   r   r   r   r   r   r   r   c              3  8   K   | ]  }d |j                   v   yw)r   Nr   r!  s     r   rC   z4test_A4_gemini_blocked_with_label.<locals>.<genexpr>  r#  r$  r%  r'  s          r   !test_A4_gemini_blocked_with_labelr6    s    	B!"CDDDvJu%t,RyD
 !&G w/F>"b(((783D EC EEFEHHHHH !Fs   Bc                     t        dd      } t        | g| d   d   t               idg i      }dh|_        t	        |      }t        ||      }|d   g k(  sJ |d	   dgk(  sJ |j                  dgk(  sJ y )
Ni   ztask/task-7777-dev1r   r   r   r   z	task-7777r   r   )r   r+   r)   r   r  r  cancelled_closed)rx   r   r  r  s       r   )test_A5_cancelled_pr_is_closed_not_mergedr:    s    	2	3BDvJu%'='?@RyD
 '-D &G w/F>"b(((>"se+++""se+++r   c            	     R   dD  cg c]  } t        | |  dz         }} t        ||D ci c]  }|d   d   t                c}|D ci c]  }|d   g 
 c}d      }t        |      }t	        ||      }|d	   g dk(  sJ |d
   D cg c]  }|d   j                  d      s| }}t               }|D ]  }	|j                  |	d           t        |      dk\  sJ |j                  D 
cg c]  }
|
j                   }}
|t        |      k(  sJ y c c} w c c}w c c}w c c}w c c}
w )N)         r   )r   r   r   r   r,   r  r   r   r   )zbefore-mergezafter-merger0      )r   r+   r)   r  r  
startswithr   addr  r   r   sorted)nr4   rA   r   r  r  r  audit_headsseen_shar   mr   s               r   test_A6_three_prs_serializedrG    s@   0?
@171QC2+&
@C
@HKL1AfIe$&<&>>L145A(R5	D !&G w/F>"o555%g.i!G*2G2GHg2h1iKiH )U;'() x=A&,mm44I4y))))) A M5 j 5s"   DD
DD!D*D$c                p   | dz  }|j                  t        d|       |j                  t        dd        t        j                  dt        ddi      }|d	k(  sJ t	        j
                  |j                         j                               }|d
   dk(  sJ |d   d	k(  sJ |d   t        k(  sJ |d   dk(  sJ y )Nzaudit.jsonl	AUDIT_LOGget_main_headc                     y)Nabc123r   )_rs    r   r   z5test_record_main_head_appends_jsonl.<locals>.<lambda>  r   r   z
test-stagefoobar)r   rL  r   r0   r   )r   r"   record_main_headr   jsonloads	read_textstrip)rj   r   log_pathr   lines        r   #test_record_main_head_appends_jsonlrW    s    -'H[(3_.AB


|T%
HC(??::h((*0023D=L((((((<4;%r   c                     ddddddddddddg} t        j                  |       \  }}d|v sJ d|v sJ t        |      d	k(  sJ t        |      d
k(  sJ y )Nr/  r   r   r.  r  r   r   r   r?  r=   )r"   required_check_stater  )r&   r1  non_successs      r   1test_required_check_state_detects_partial_successr[    s    95	2%Y?95	D 33D9G[$$$7***w<1{q   r   )r   rH   r   r^   r   r^   r   r^   r   r^   r_   r`   )r%   ztuple[str, str]r_   r]   )rj   r   r_   ra   )r_   ra   )r   pytest.MonkeyPatchr_   ra   )r   r+   )r   r+   r  r`   )rj   r   r   r\  r_   ra   )7re   
__future__r   rQ  r   syspathlibr   typingr   unittestr   ro   __file__resolveparents	WORKSPACESCRIPTSrM   insertr^   auto_merge_controllerr"   auto_merge_lockr	   r
   r   r   r)   r+   rk   rs   r   r   r   r   r   r   r   r   r   r   r   r   r   r  r  r  r*  r2  r6  r:  rG  rW  r[  r   r   r   <module>rj     sb   #   
    N""$,,Q/	
i
 3w<   3y> " # 1  ("  	
 
  &1/ 1/r	"F
7
<

,$)*
"
22'+
.$6r
(*0K">$I , *:
 !r   