
    aje                       d Z ddlmZ ddlZddlmc mZ ddl	m
Z
 ddlmZmZ ddlZddlmZ ddlmZmZmZmZmZmZmZmZmZmZmZmZmZmZm Z  ddl!m"Z" dZ#d	Z$d
dgZ%e#dddddd	 	 	 	 	 	 	 	 	 	 	 	 	 d&dZ&d'dZ'dddd
ddd	 	 	 	 	 	 	 	 	 	 	 	 	 d(dZ(d Z)d Z*d Z+d Z,d Z-d Z.d Z/d Z0d Z1d  Z2d! Z3d" Z4d# Z5d$ Z6d% Z7y))u  tests/regression/test_ci_gemini_watcher_runner_2718.py — task-2718 회귀 테스트 스위트.

회장 verbatim 매핑 (시나리오 1~13):
  1.  test_merge_ready_candidate          — FRESH + escalated 0 + CI SUCCESS → MERGE_READY_CANDIDATE
  2.  test_loop_boundary_on_escalation    — FRESH + escalated ≥1 → LOOP_BOUNDARY
  3.  test_hold_stale_head                — actual_head != expected_head → HOLD_STALE_HEAD
  4.  test_hold_scope_unclean             — diff_paths 에 scope 외 경로 → HOLD_SCOPE_UNCLEAN
  5.  test_ci_failed_non_remediable       — CI FAILURE non-remediable → CI_FAILED_NON_REMEDIABLE
  6.  test_allow_owner_trigger            — STALE + is_owner + dedupe False → ALLOW_OWNER_TRIGGER
  7.  test_external_trigger_required_without_owner — STALE + owner None → GEMINI_EXTERNAL_TRIGGER_REQUIRED
  8.  test_self_key_blocked               — STALE + self_key=True → BLOCKED_BY_CAPABILITY / SELF_GEMINI_TRIGGER_BLOCKED
  9.  test_blocked_by_capability_no_gh_runner — gh_runner=None → BLOCKED_BY_CAPABILITY
  10. test_auto_gemini_triage_called      — FRESH 경로에서 AutoGeminiTriage.triage_batch mock 주입 후 호출 검증
  11. test_owner_trigger_decision_validate_called — STALE+owner 경로에서 validate_decision spy 검증
  12. test_github_write_zero              — 모든 주요 경로에서 github_writes == 0
  13. test_idempotent_dedupe              — 동일 head 2회 호출, 2회차 dedupe → OWNER_TRIGGER_DEDUPED
    )annotationsN)AnyCallable)AutoGeminiTriage)ALL_TERMINALSTERMINAL_BLOCKED_BY_CAPABILITY!TERMINAL_CI_FAILED_NON_REMEDIABLE)TERMINAL_GEMINI_EXTERNAL_TRIGGER_REQUIREDTERMINAL_HOLD_SCOPE_UNCLEANTERMINAL_HOLD_STALE_HEADTERMINAL_LOOP_BOUNDARYTERMINAL_MERGE_READY_CANDIDATETRIGGER_ALLOW_OWNERTRIGGER_EXTERNAL_REQUIREDTRIGGER_NONETRIGGER_OWNER_DEDUPEDTRIGGER_SELF_BLOCKEDWatchResultrun_watch_cycle)validate_decision(aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa(bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbz"anu_v2/ci_gemini_watcher_runner.pyzanu_v2/auto_gemini_triage.pySUCCESST)actual_head
diff_pathsci_stateci_remediablereviewsfindingsc                f    	 ||nt        t              ||ng 	||ng d	 fd}|S )u=   op 분기 dispatch 하는 gh_runner closure 를 반환한다.c                l    | dk(  rS | dk(  rS | dk(  rdS | dk(  rS | dk(  rS t        d|       )Nr   r   	ci_rollup)state
remediabler   r   zunknown op: )
ValueError)opparams_diff_paths	_findings_reviewsr   r   r   s     J/home/jay/workspace/tests/regression/test_ci_gemini_watcher_runner_2718.py	gh_runnerz!make_gh_runner.<locals>.gh_runnerJ   s^    %]CC?O<v.//    )r&   strr'   r   returnr   )listDEFAULT_FILES)
r   r   r   r   r   r   r,   r(   r)   r*   s
   ` ``   @@@r+   make_gh_runnerr2   <   s@     !+ 6*D<OK!-w2H$0bI0 0 r-   c                    ddi| dddS )zCFRESH fixture: gemini-code-assist[bot] review with given commit_id.loginzgemini-code-assist[bot]	COMMENTEDzLooks good.)user	commit_idr#   body )r7   s    r+   make_gemini_reviewr:   [   s      34	 r-   zno-oplowstyle rule_idseveritycategorypathr8   titlec                    | |||||dS )u   finding fixture 헬퍼.r>   r9   r>   s         r+   make_findingrE   e   s       r-   c            	     0
   t        t              g} t        ddt        d         g}t	        t        t        t              d| |      }t        dt        t        |d	      }|j                  }|t        k(  }|st        j                  d
|fd|t        f      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      dt        j                         v st        j                  t              rt        j                  t              nddz  }dd|iz  }t        t        j                   |            dx}}|j"                  }d}||k(  }|st        j                  d
|fd||f      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      dz  }	dd|	iz  }
t        t        j                   |
            dx}x}}|j$                  }d}||u }|st        j                  d|fd||f      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      dz  }	dd|	iz  }
t        t        j                   |
            dx}x}}|j&                  }d}||k(  }|st        j                  d
|fd||f      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      dz  }	dd|	iz  }
t        t        j                   |
            dx}x}}|j(                  } |       }t+        |t,              }|s3ddt        j                         v st        j                  t*              rt        j                  t*              nddt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      dt        j                         v st        j                  t,              rt        j                  t,              ndt        j                  |      dz  }t        t        j                   |            dx}x}}y)uP   FRESH review + escalated 0 + CI SUCCESS + scope clean → MERGE_READY_CANDIDATE.r;   r<   r   )r@   rA   rB   r   r   r   r   r   r   *   T	pr_numberexpected_headexpected_filesr,   dry_run==z0%(py2)s
{%(py2)s = %(py0)s.terminal
} == %(py4)sresultr   py0py2py4assert %(py6)spy6Nz5%(py2)s
{%(py2)s = %(py0)s.github_writes
} == %(py5)srS   rT   py5assert %(py7)spy7is)z/%(py2)s
{%(py2)s = %(py0)s.dry_run
} is %(py5)s)z1%(py2)s
{%(py2)s = %(py0)s.pr_number
} == %(py5)sziassert %(py8)s
{%(py8)s = %(py0)s(%(py5)s
{%(py5)s = %(py3)s
{%(py3)s = %(py1)s.to_json
}()
}, %(py6)s)
}
isinstancedict)rS   py1py3rZ   rW   py8)r:   HEAD_ArE   r1   r2   r0   r   terminalr   
@pytest_ar_call_reprcompare@py_builtinslocals_should_repr_global_name	_safereprAssertionError_format_explanationgithub_writesrM   rJ   to_jsonr_   r`   )r   r   runnerrQ   @py_assert1@py_assert3@py_format5@py_format7@py_assert4@py_format6@py_format8@py_assert2@py_assert7@py_format9s                 r+   test_merge_ready_candidater{   }   s   !&)*GegMRSDTUVH&F $F ??<?<<<<<?<<<<<<<6<<<6<<<?<<<<<<<<<<<<<<<<<<$1$1$$$$1$$$$$$6$$$6$$$$$$1$$$$$$$>>!T!>T!!!!>T!!!!!!6!!!6!!!>!!!T!!!!!!!!r!r!!!!r!!!!!!6!!!6!!!!!!r!!!!!!!nn-n&-:&--------:---:------f---f---n---&-------------------r-   c                    t        t              g} t        dddd      g}t        t        t	        t
              d| |      }t        dt        t
        |d	
      }|j                  }|t        k(  }|st        j                  d|fd|t        f      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      dt        j                         v st        j                  t              rt        j                  t              nddz  }dd|iz  }t        t        j                   |            dx}}|j"                  }d}||u}|st        j                  d|fd||f      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      dz  }	dd|	iz  }
t        t        j                   |
            dx}x}}|j"                  d   }d}||k\  }|slt        j                  d|fd||f      t        j                  |      t        j                  |      dz  }dd|iz  }t        t        j                   |            dx}x}}|j$                  }d}||k(  }|st        j                  d|fd||f      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      dz  }	dd|	iz  }
t        t        j                   |
            dx}x}}y)u   FRESH review + scope_expansion finding(out-of-scope path) → escalated ≥1 → LOOP_BOUNDARY.
    진짜 AutoGeminiTriage 를 사용해 escalation 분류를 실증한다.
    zreal-bug-001highbugzsome/other/file.py)r?   r@   rA   rB   r   rG   rH   TrI   rN   rP   rQ   r   rR   rV   rW   Nis not)z:%(py2)s
{%(py2)s = %(py0)s.triage_summary
} is not %(py5)srY   r[   r\   escalated_count   )>=)z%(py1)s >= %(py4)sra   rU   r   rX   )r:   rd   rE   r2   r0   r1   r   re   r   rf   rg   rh   ri   rj   rk   rl   rm   triage_summaryrn   )r   r   rp   rQ   rq   rr   rs   rt   ru   rv   rw   @py_assert0rx   s                r+    test_loop_boundary_on_escalationr      s&    "&)*G!	 H &F $F ??4?44444?444444464446444?444444444444444444  ,, ,,,, ,,,,,,6,,,6,,, ,,,,,,,,,,  !238q83q88883q8883888q8888888$1$1$$$$1$$$$$$6$$$6$$$$$$1$$$$$$$r-   c                    t        t              } t        dt        t        |       }|j
                  }|t        k(  }|st        j                  d|fd|t        f      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      dt        j                         v st        j                  t              rt        j                  t              nddz  }d	d
|iz  }t        t        j                  |            dx}}|j                  }|t        k(  }|st        j                  d|fd|t        f      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      dt        j                         v st        j                  t              rt        j                  t              nddz  }d	d
|iz  }t        t        j                  |            dx}}|j                   }d}||k(  }|st        j                  d|fd||f      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      dz  }dd|iz  }t        t        j                  |            dx}x}}y)u:   actual_head != expected_head → terminal HOLD_STALE_HEAD.)r   r   rJ   rK   rL   r,   rN   rP   rQ   r   rR   rV   rW   N)z3%(py2)s
{%(py2)s = %(py0)s.actual_head
} == %(py4)sHEAD_Br   rX   rY   r[   r\   )r2   r   r   rd   r1   re   r   rf   rg   rh   ri   rj   rk   rl   rm   r   rn   	rp   rQ   rq   rr   rs   rt   ru   rv   rw   s	            r+   test_hold_stale_headr      s   /F$	F ??6?66666?666666666666666?666666666666666666'''''''''''6'''6'''''''''''''''''''$1$1$$$$1$$$$$$6$$$6$$$$$$1$$$$$$$r-   c                    t        t              dgz   } t        t        |       }t	        dt        t        |      }|j
                  }|t        k(  }|st        j                  d|fd|t        f      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      dt        j                         v st        j                  t              rt        j                  t              ndd	z  }d
d|iz  }t        t        j                  |            dx}}|j                  }d}||k(  }|st        j                  d|fd||f      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      dz  }dd|iz  }	t        t        j                  |	            dx}x}}y)uG   diff_paths 에 expected_files 밖 경로 포함 → HOLD_SCOPE_UNCLEAN.zscripts/intruder.sh)r   r      r   rN   rP   rQ   r   rR   rV   rW   Nr   rX   rY   r[   r\   )r0   r1   r2   rd   r   re   r   rf   rg   rh   ri   rj   rk   rl   rm   rn   )
diffrp   rQ   rq   rr   rs   rt   ru   rv   rw   s
             r+   test_hold_scope_uncleanr      s+   "7!88D4@F$	F ??9?99999?999999969996999?999999999999999999$1$1$$$$1$$$$$$6$$$6$$$$$$1$$$$$$$r-   c                    t        t        t        t              dd      } t	        dt        t        |       }|j
                  }|t        k(  }|st        j                  d|fd|t        f      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      d	t        j                         v st        j                  t              rt        j                  t              nd	d
z  }dd|iz  }t        t        j                  |            dx}}|j                  }d}||k(  }|st        j                  d|fd||f      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      dz  }dd|iz  }t        t        j                  |            dx}x}}|j                   }d}||k(  }|st        j                  d|fd||f      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      dz  }dd|iz  }t        t        j                  |            dx}x}}y)u;   CI FAILURE + remediable=False → CI_FAILED_NON_REMEDIABLE.FAILUREF)r   r   r   r   
   r   rN   rP   rQ   r	   rR   rV   rW   N)z0%(py2)s
{%(py2)s = %(py0)s.ci_state
} == %(py5)srY   r[   r\   r   rX   )r2   rd   r0   r1   r   re   r	   rf   rg   rh   ri   rj   rk   rl   rm   r   rn   r   s	            r+   test_ci_failed_non_remediabler      s   &	F $	F ?????????????????6???6????????????????????????'i'?i''''?i''''''6'''6'''?'''i'''''''$1$1$$$$1$$$$$$6$$$6$$$$$$1$$$$$$$r-   c                 *   t        t              g} t        t        t	        t
              d|       }ddd}g d(fd}t        dt        t
        ||d |dd		      }|j                  }|t        k(  }|st        j                  d
|fd|t        f      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      dt        j                         v st        j                  t              rt        j                  t              nddz  }dd|iz  }t        t        j                   |            dx}}|j"                  }|t$        k(  }|st        j                  d
|fd|t$        f      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      dt        j                         v st        j                  t$              rt        j                  t$              nddz  }dd|iz  }t        t        j                   |            dx}}|j&                  }d}	||	u}|st        j                  d|fd||	f      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |	      dz  }
dd|
iz  }t        t        j                   |            dx}x}}	|j(                  }d}	||	k(  }|st        j                  d
|fd||	f      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |	      dz  }
dd|
iz  }t        t        j                   |            dx}x}}	t+        |j&                  |j,                        }|j&                  }||u }|st        j                  d|fd||f      dt        j                         v st        j                  |      rt        j                  |      nddt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      dz  }dd|iz  }t        t        j                   |            dx}}t/              }d}||k(  }	|	st        j                  d
|	fd ||f      d!t        j                         v st        j                  t.              rt        j                  t.              nd!d"t        j                         v st        j                        rt        j                        nd"t        j                  |      t        j                  |      d#z  }d$d%|iz  }t        t        j                   |            dx}x}	}d   }dt        f}||k(  }|slt        j                  d
|fd&||f      t        j                  |      t        j                  |      d'z  }dd|iz  }t        t        j                   |            dx}x}}y))u   STALE review + is_owner=True/admin=True + dedupe False → ALLOW_OWNER_TRIGGER.
    decision_json 이 owner_trigger_decision.v1 schema 를 통과하는지 validate_decision 재검증.
    terminal == GEMINI_EXTERNAL_TRIGGER_REQUIRED.
    r   )r   r   r   r   Tis_owneradminc                ,    j                  | |f       y N)append)prsharecordeds     r+   record_triggerz0test_allow_owner_trigger.<locals>.record_trigger  s    S	"r-      c                     yNFr9   r   r   s     r+   <lambda>z*test_allow_owner_trigger.<locals>.<lambda>      r-   z	task-2718)	rJ   rK   rL   r,   owner_proofdedupe_checkerr   task_idrM   rN   z8%(py2)s
{%(py2)s = %(py0)s.trigger_decision
} == %(py4)srQ   r   rR   rV   rW   NrP   r
   r   z9%(py2)s
{%(py2)s = %(py0)s.decision_json
} is not %(py5)srY   r[   r\   r   rX   )current_head_actualr]   )z5%(py0)s is %(py4)s
{%(py4)s = %(py2)s.decision_json
}	validatedr   z0%(py3)s
{%(py3)s = %(py0)s(%(py1)s)
} == %(py6)slenr   rS   ra   rb   rW   assert %(py8)src   z%(py1)s == %(py4)sr   r   intr   r.   r/   None)r:   r   r2   rd   r0   r1   r   trigger_decisionr   rf   rg   rh   ri   rj   rk   rl   rm   re   r
   decision_jsonrn   r   r   r   )r   rp   r   r   rQ   rq   rr   rs   rt   ru   rv   rw   r   rx   @py_assert5rz   r   r   s                    @r+   test_allow_owner_triggerr     s    "&)*G&	F  $d3KH# $,%
F ""9"&99999"&999999969996999"999999&9999&99999999??G?GGGGG?GGGGGGG6GGG6GGG?GGGGGGGGGGGGGGGGGG+t+t++++t++++++6+++6++++++t+++++++$1$1$$$$1$$$$$$6$$$6$$$$$$1$$$$$$$ "&"6"6FL^L^_I,,,9,,,,,9,,,,,,,9,,,9,,,,,,,,,,,,,,,,,,,, x=A=A=A33xx=AA;&2v,&;,&&&&;,&&&;&&&,&&&&&&&r-   c                    t        t              g} t        t        t	        t
              |       }t        dt        t
        |d      }|j                  }|t        k(  }|st        j                  d|fd|t        f      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      dt        j                         v st        j                  t              rt        j                  t              ndd	z  }d
d|iz  }t        t        j                   |            dx}}|j"                  }|t$        k(  }|st        j                  d|fd|t$        f      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      dt        j                         v st        j                  t$              rt        j                  t$              ndd	z  }d
d|iz  }t        t        j                   |            dx}}|j&                  }d}||u }|st        j                  d|fd||f      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      dz  }dd|iz  }	t        t        j                   |	            dx}x}}|j(                  }d}||k(  }|st        j                  d|fd||f      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      dz  }dd|iz  }	t        t        j                   |	            dx}x}}y)ux   STALE review + owner_proof=None → terminal GEMINI_EXTERNAL_TRIGGER_REQUIRED, trigger GEMINI_EXTERNAL_TRIGGER_REQUIRED.r   r   r      NrJ   rK   rL   r,   r   rN   rP   rQ   r
   rR   rV   rW   r   r   r]   )z5%(py2)s
{%(py2)s = %(py0)s.decision_json
} is %(py5)srY   r[   r\   r   rX   )r:   r   r2   rd   r0   r1   r   re   r
   rf   rg   rh   ri   rj   rk   rl   rm   r   r   r   rn   
r   rp   rQ   rq   rr   rs   rt   ru   rv   rw   s
             r+   ,test_external_trigger_required_without_ownerr   5  s-   !&)*G&F $F ??G?GGGGG?GGGGGGG6GGG6GGG?GGGGGGGGGGGGGGGGGG""?"&?????"&???????6???6???"??????&????&????????'4'4''''4''''''6'''6''''''4'''''''$1$1$$$$1$$$$$$6$$$6$$$$$$1$$$$$$$r-   c            	        t        t              g} t        t        t	        t
              |       }t        dt        t
        |ddd      }|j                  }|t        k(  }|st        j                  d|fd|t        f      d	t        j                         v st        j                  |      rt        j                  |      nd	t        j                  |      d
t        j                         v st        j                  t              rt        j                  t              nd
dz  }dd|iz  }t        t        j                   |            dx}}|j"                  }|t$        k(  }|st        j                  d|fd|t$        f      d	t        j                         v st        j                  |      rt        j                  |      nd	t        j                  |      dt        j                         v st        j                  t$              rt        j                  t$              nddz  }dd|iz  }t        t        j                   |            dx}}y)uN   STALE + is_owner=False → GEMINI_EXTERNAL_TRIGGER_REQUIRED (owner 불충분).r      FTr   r   rN   rP   rQ   r
   rR   rV   rW   Nr   r   )r:   r   r2   rd   r0   r1   r   re   r
   rf   rg   rh   ri   rj   rk   rl   rm   r   r   )r   rp   rQ   rq   rr   rs   rt   s          r+   -test_external_trigger_required_is_owner_falser   L  sH   !&)*G4;NX_`F$!&6F ??G?GGGGG?GGGGGGG6GGG6GGG?GGGGGGGGGGGGGGGGGG""?"&?????"&???????6???6???"??????&????&????????r-   c            
     .   t        t              g} t        t        t	        t
              |       }t        dt        t
        |dddd      }|j                  }|t        k(  }|st        j                  d|fd|t        f      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      d	t        j                         v st        j                  t              rt        j                  t              nd	d
z  }dd|iz  }t        t        j                   |            dx}}|j"                  }|t$        k(  }|st        j                  d|fd|t$        f      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      dt        j                         v st        j                  t$              rt        j                  t$              ndd
z  }dd|iz  }t        t        j                   |            dx}}|j&                  }d}||k(  }|st        j                  d|fd||f      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      dz  }dd|iz  }	t        t        j                   |	            dx}x}}y)uZ   STALE + owner_proof.self_key=True → BLOCKED_BY_CAPABILITY / SELF_GEMINI_TRIGGER_BLOCKED.r   (   T)r   r   self_keyr   rN   rP   rQ   r   rR   rV   rW   Nr   r   r   rX   rY   r[   r\   )r:   r   r2   rd   r0   r1   r   re   r   rf   rg   rh   ri   rj   rk   rl   rm   r   r   rn   r   s
             r+   test_self_key_blockedr   a  s   !&)*G4;NX_`F$!%$GF ??<?<<<<<?<<<<<<<6<<<6<<<?<<<<<<<<<<<<<<<<<<"":"&:::::"&:::::::6:::6:::"::::::&::::&::::::::$1$1$$$$1$$$$$$6$$$6$$$$$$1$$$$$$$r-   c                    t        dt        t        d      } | j                  }|t        k(  }|st        j                  d|fd|t        f      dt        j                         v st        j                  |       rt        j                  |       ndt        j                  |      dt        j                         v st        j                  t              rt        j                  t              nddz  }d	d
|iz  }t        t        j                  |            dx}}| j                  }|t        k(  }|st        j                  d|fd|t        f      dt        j                         v st        j                  |       rt        j                  |       ndt        j                  |      dt        j                         v st        j                  t              rt        j                  t              nddz  }d	d
|iz  }t        t        j                  |            dx}}| j                  }d}||k(  }|st        j                  d|fd||f      dt        j                         v st        j                  |       rt        j                  |       ndt        j                  |      t        j                  |      dz  }dd|iz  }t        t        j                  |            dx}x}}y)uD   gh_runner=None → capability gate 차단 → BLOCKED_BY_CAPABILITY.2   Nr   rN   rP   rQ   r   rR   rV   rW   r   r   r   rX   rY   r[   r\   )r   rd   r1   re   r   rf   rg   rh   ri   rj   rk   rl   rm   r   r   rn   )rQ   rq   rr   rs   rt   ru   rv   rw   s           r+   'test_blocked_by_capability_no_gh_runnerr   w  s   $	F ??<?<<<<<?<<<<<<<6<<<6<<<?<<<<<<<<<<<<<<<<<<""2"l2222"l22222262226222"222222l222l2222222$1$1$$$$1$$$$$$6$$$6$$$$$$1$$$$$$$r-   c                 `	   t        t              g} t        t        d         g}t	        t        t        t              d| |      }t        j                  t              }g g g d|j                  _
        t        dt        t        ||      }|j                  j                          |j                  j                  }|d   d   }|d   d	   }||k(  }|st        j                  d
|fd||f      dt!        j"                         v st        j$                  |      rt        j&                  |      nddt!        j"                         v st        j$                  |      rt        j&                  |      nddz  }	dd|	iz  }
t)        t        j*                  |
            d}t        |      }|t        k(  }|s/t        j                  d
|fd|t        f      dt!        j"                         v st        j$                  t
              rt        j&                  t
              nddt!        j"                         v st        j$                  |      rt        j&                  |      ndt        j&                  |      dt!        j"                         v st        j$                  t              rt        j&                  t              nddz  }dd|iz  }t)        t        j*                  |            dx}}|j,                  }|t.        k(  }|st        j                  d
|fd|t.        f      dt!        j"                         v st        j$                  |      rt        j&                  |      ndt        j&                  |      dt!        j"                         v st        j$                  t.              rt        j&                  t.              nddz  }
dd|
iz  }t)        t        j*                  |            dx}}|j0                  }d}||k(  }|st        j                  d
|fd||f      dt!        j"                         v st        j$                  |      rt        j&                  |      ndt        j&                  |      t        j&                  |      d z  }dd|iz  }t)        t        j*                  |            dx}x}}y)!uf   FRESH 경로에서 triage= 로 MagicMock(spec=AutoGeminiTriage) 주입 후 triage_batch 호출 검증.r   )rB   r   rG   )spec)applied	dismissed	escalated<   )rJ   rK   rL   r,   triager   rN   )z%(py0)s == %(py2)scalled_findingsr   )rS   rT   zassert %(py4)srU   N)z0%(py3)s
{%(py3)s = %(py0)s(%(py1)s)
} == %(py5)sr0   called_expectedr1   )rS   ra   rb   rZ   r[   r\   rP   rQ   r   rR   rV   rW   rX   rY   )r:   rd   rE   r1   r2   r0   mock	MagicMockr   triage_batchreturn_valuer   assert_called_once	call_argsrf   rg   rh   ri   rj   rk   rl   rm   re   r   rn   )r   r   rp   mock_triagerQ   r   r   r   rq   @py_format3rs   rx   ru   rv   rw   rr   rt   s                    r+   test_auto_gemini_triage_calledr     s   !&)*G-"234H&F ..&67K8:Z\,]K)$F //1((22Il1oOl1oOh&&&&?h&&&&&&?&&&?&&&&&&h&&&h&&&&&&& 1 M1111 M11111141114111111111111 111111M111M1111111 ??<?<<<<<?<<<<<<<6<<<6<<<?<<<<<<<<<<<<<<<<<<$1$1$$$$1$$$$$$6$$$6$$$$$$1$$$$$$$r-   c            
        t        t              g} t        t        t	        t
              |       }t        j                  dt              5 }t        dt        t
        |dddd d	      }d
d
d
       j                          j                  }d
}||u}|st        j                  d|fd||f      dt        j                         v st        j                   |      rt        j"                  |      ndt        j"                  |      t        j"                  |      dz  }dd|iz  }t%        t        j&                  |            d
x}x}}|j(                  }|t*        k(  }|st        j                  d|fd|t*        f      dt        j                         v st        j                   |      rt        j"                  |      ndt        j"                  |      dt        j                         v st        j                   t*              rt        j"                  t*              nddz  }	dd|	iz  }
t%        t        j&                  |
            d
x}}y
# 1 sw Y   xY w)um   STALE+owner 경로에서 validate_decision 이 실제 호출되어 decision_json 생성됨을 spy 로 입증.r   z1anu_v2.ci_gemini_watcher_runner.validate_decision)wrapsF   Tr   c                     yr   r9   r   s     r+   r   z=test_owner_trigger_decision_validate_called.<locals>.<lambda>  r   r-   ztask-2718-spy)rJ   rK   rL   r,   r   r   r   Nr   r   rQ   rY   r[   r\   rN   r   r   rR   rV   rW   )r:   r   r2   rd   r0   r1   r   patchr   r   r   r   rf   rg   rh   ri   rj   rk   rl   rm   r   r   )r   rp   spyrQ   rq   ru   rr   rv   rw   rs   rt   s              r+   +test_owner_trigger_decision_validate_calledr     s~   !&)*G&F 
;
 
 
  (%)D90#
	
 +t+t++++t++++++6+++6++++++t+++++++""9"&99999"&999999969996999"999999&9999&99999999!
 
s   IIc                 n   t        dt        t        t        t        t	        t              dt        t              gt        d      g            } | j                  }d}||k(  }|st        j                  d|fd	||f      d
t        j                         v st        j                  |       rt        j                  |       nd
t        j                  |      t        j                  |      dz  }dd|iz  }t        t        j                  |            dx}x}}| j                   }|t"        k(  }|st        j                  d|fd|t"        f      d
t        j                         v st        j                  |       rt        j                  |       nd
t        j                  |      dt        j                         v st        j                  t"              rt        j                  t"              nddz  }dd|iz  }t        t        j                  |            dx}}t        dt        t        t        t        t	        t              t        t$              g      dddd       }|j                  }d}||k(  }|st        j                  d|fd	||f      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      dz  }dd|iz  }t        t        j                  |            dx}x}}|j&                  }|t(        k(  }|st        j                  d|fd|t(        f      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      dt        j                         v st        j                  t(              rt        j                  t(              nddz  }dd|iz  }t        t        j                  |            dx}}t        dt        t        t        t        t	        t              t        t$              g      d      }	|	j                  }d}||k(  }|st        j                  d|fd	||f      dt        j                         v st        j                  |	      rt        j                  |	      ndt        j                  |      t        j                  |      dz  }dd|iz  }t        t        j                  |            dx}x}}|	j&                  }|t*        k(  }|st        j                  d|fd|t*        f      dt        j                         v st        j                  |	      rt        j                  |	      ndt        j                  |      d t        j                         v st        j                  t*              rt        j                  t*              nd dz  }dd|iz  }t        t        j                  |            dx}}y)!uS   merge_ready, allow_owner, external_required 모든 경로에서 github_writes == 0.P   r   r<   )rA   rG   r   r   rN   rX   r1rY   r[   r\   NrP   r   rR   rV   rW   Q   r   Tr   c                     yr   r9   r   s     r+   r   z(test_github_write_zero.<locals>.<lambda>  r   r-   )rJ   rK   rL   r,   r   r   r2r   r   R   r   r3r   )r   rd   r1   r2   r0   r:   rE   rn   rf   rg   rh   ri   rj   rk   rl   rm   re   r   r   r   r   r   )
r   rq   ru   rr   rv   rw   rs   rt   r   r   s
             r+   test_github_write_zeror     s    
$ M*'/0"G45
	
B  q q    q      2   2      q       ;;8;88888;888888828882888;888888888888888888 
$ M*'/0

 "&5,
B  q q    q      2   2      q       5"55555"555555525552555555555"5555"55555555 
$ M*'/0

 

B  q q    q      2   2      q       ;";;;;;";;;;;;;2;;;2;;;;;;;;;";;;;";;;;;;;;r-   c            
     
   t        t              g} t        t        t	        t
              |       }ddd}t               dfd}g d fd}t        dt        t
        ||||d      }|j                  }|t        k(  }|st        j                  d	|fd
|t        f      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      dt        j                         v st        j                  t              rt        j                  t              nddz  }dd|iz  }	t!        t        j"                  |	            dx}}t%              }
d}|
|k(  }|st        j                  d	|fd|
|f      dt        j                         v st        j                  t$              rt        j                  t$              nddt        j                         v st        j                        rt        j                        ndt        j                  |
      t        j                  |      dz  }	dd|	iz  }t!        t        j"                  |            dx}
x}}t        dt        t
        ||||d      }|j                  }|t&        k(  }|st        j                  d	|fd
|t&        f      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      dt        j                         v st        j                  t&              rt        j                  t&              nddz  }dd|iz  }	t!        t        j"                  |	            dx}}t%              }
d}|
|k(  }|st        j                  d	|fd|
|f      dt        j                         v st        j                  t$              rt        j                  t$              nddt        j                         v st        j                        rt        j                        ndt        j                  |
      t        j                  |      dz  }	dd|	iz  }t!        t        j"                  |            dx}
x}}|j(                  }d}||k(  }|st        j                  d	|fd||f      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      dz  }dd|iz  }t!        t        j"                  |            dx}x}}y)!u   동일 head 2회 호출: 1회차 ALLOW_OWNER_TRIGGER → record_trigger 호출. 2회차 dedupe → OWNER_TRIGGER_DEDUPED. record_trigger 총 호출 횟수 == 1.r   Tr   c                    | |fv S r   r9   )r   r   triggered_sets     r+   r   z.test_idempotent_dedupe.<locals>.dedupe_checker  s    CyM))r-   c                R    j                  | |f       j                  | |f       y r   )addr   )r   r   recorded_callsr   s     r+   r   z.test_idempotent_dedupe.<locals>.record_trigger  s'    2s)$r3i(r-   Z   ztask-2718-dedupe)rJ   rK   rL   r,   r   r   r   r   rN   r   r   r   rR   rV   rW   Nr   r   r   r   r   r   rc   r   r   r   rX   rY   r[   r\   )r   r   r   r.   r/   boolr   )r:   r   r2   rd   r0   r1   setr   r   r   rf   rg   rh   ri   rj   rk   rl   rm   r   r   rn   )r   rp   r   r   r   r   rq   rr   rs   rt   rx   r   ru   rz   r   rv   rw   r   r   s                    @@r+   test_idempotent_deduper   	  s6   !&)*G&F
  $d3K +.%M* #%N)
 
$%%"	
B 5"55555"555555525552555555555"5555"55555555~#!#!####!######3###3######~###~######!####### 
$%%"	
B 7"77777"777777727772777777777"7777"77777777~#!#!####!######3###3######~###~######!####### q q    q      2   2      q       r-   c                 \   t        dt        t        t        t        t	        t              dt        t              gg             } | j                         }t        |t              }|sddt        j                         v st        j                  t              rt        j                  t              nddt        j                         v st        j                  |      rt        j                  |      nddt        j                         v st        j                  t              rt        j                  t              ndt        j                  |      d	z  }t        t        j                  |            d
}|d   }|t         v }|st        j"                  d|fd|t         f      t        j                  |      dt        j                         v st        j                  t               rt        j                  t               nddz  }dd|iz  }t        t        j                  |            d
x}}|d   }d}||k(  }|slt        j"                  d|fd||f      t        j                  |      t        j                  |      dz  }dd|iz  }t        t        j                  |            d
x}x}}y
)uf   WatchResult.to_json() 이 dict 를 반환하고 모든 terminal 상수가 ALL_TERMINALS 에 포함됨.c   r   rG   r   z5assert %(py4)s
{%(py4)s = %(py0)s(%(py1)s, %(py2)s)
}r_   dr`   )rS   ra   rT   rU   Nre   )in)z%(py1)s in %(py3)sr   )ra   rb   zassert %(py5)srZ   rn   r   rN   r   r   rV   rW   )r   rd   r1   r2   r0   r:   ro   r_   r`   rh   ri   rf   rj   rk   rl   rm   r   rg   )	rQ   r   rr   rs   r   rx   @py_format4rv   rt   s	            r+   &test_watch_result_to_json_serializabler   B  s~   $ M*'/0
	F 	Aa::aaZ=)=M))))=M)))=))))))M)))M)))))))_"""""""""""""""""""r-   )r   r.   r   zlist[str] | Noner   r.   r   r   r   list[dict] | Noner   r   r/   zCallable[..., Any])r7   r.   r/   r`   )r?   r.   r@   r.   rA   r.   rB   r.   r8   r.   rC   r.   r/   r`   )8__doc__
__future__r   builtinsrh   _pytest.assertion.rewrite	assertionrewriterf   unittest.mockr   typingr   r   pytestanu_v2.auto_gemini_triager   anu_v2.ci_gemini_watcher_runnerr   r   r	   r
   r   r   r   r   r   r   r   r   r   r   r   anu_v2.owner_trigger_decisionr   rd   r   r1   r2   r:   rE   r{   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r9   r-   r+   <module>r     si  $ #        6    " <
 
	 67UV #'!%"& ! 	
     > 4  	
    
0.@%L%(%(%2*'b%.@*%,%$#%T:@/<l2!r#r-   