
    j                       d Z ddlmZ ddlZddlmc mZ ddl	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mZmZ ddlmZmZmZmZmZmZm Z m!Z!m"Z" ddl#m$Z$ dd	l%m&Z& dd
l'm(Z( ddl)m*Z* dZ+de+dddd	 	 	 	 	 	 	 	 	 	 	 ddZ,ddZ-dd	 	 	 	 	 	 	 d dZ.d Z/d Z0d Z1d Z2d Z3d Z4d Z5d Z6y)!u  task-2556 §3 — 30min timeout 후 FIRST_GEMINI_TRIGGER_MISSING 자동 trigger 회귀.

회장 §명시 2026-05-12 §3:
  PR createdAt + 30min 경과 + Gemini reviews 0건 = FIRST_GEMINI_TRIGGER_MISSING.

검증 포인트:
  1. 정확 grace period (30분) 경계 검증 (within / boundary / past).
  2. boundary 직전 (29:59) → WITHIN_GRACE_PERIOD, scheduler 는 SKIP.
  3. 30:00 정각 + reviews 0 → FIRST_GEMINI_TRIGGER_MISSING.
  4. 1h 경과 + reviews 0 → FIRST_GEMINI_TRIGGER_MISSING + scheduler OWNER_TRIGGER_DISPATCHED.
  5. 30:01 경과 + 1 review 도착 (head 가 review commit_id 와 일치) → GEMINI_FRESH_ON_HEAD → SKIP.
    )annotationsN)Path   )!ACTION_FIRST_TRIGGER_PENDING_SKIPACTION_OWNER_TRIGGER_DISPATCHEDACTION_WITHIN_GRACEACTION_FRESH_RESUMEExecutorScheduler)	$FIRST_TRIGGER_PENDING_WINDOW_SECONDSGeminiReviewMetaGRACE_PERIOD_SECONDSIdlePRDiagnoserIdlePRSnapshot"STATE_FIRST_GEMINI_TRIGGER_MISSINGSTATE_FIRST_TRIGGER_PENDINGSTATE_GEMINI_FRESH_ON_HEADSTATE_WITHIN_GRACE_PERIOD)FIRST_TIMEOUT_SECONDS)MergeQueueExecutor)OwnerTriggerAudit)OwnerTriggerOnly(aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa   2026-05-12T10:00:00+00:00 Tnumberhead_sha
created_atreviewsci_passc                $    t        | |d|||      S )Nztask/task-2556-dev5)r   r   head_refr   gemini_reviewsci_required_all_success)r   r   s        Y/home/jay/workspace/scripts/../anu_v2/tests/test_executor_first_gemini_trigger_missing.py	_snapshotr'   3   s#     & '     c               @    fd}t        | |d t        |             S )Nc                6    j                  | ||d       ddiS )N)methodpathbodyid   )append)r+   r,   r-   headers
http_callss       r&   	http_postz_make_runner.<locals>.http_postF   s"    VT4HIayr(   c                      y)N#ghp_owner_fake_xxxxxxxxxxxxxxxxxxxxr   r   r(   r&   <lambda>z_make_runner.<locals>.<lambda>M       r(   )workspace_rootr3   token_provideraudit)r   r   )tmp_pathr2   r3   s    ` r&   _make_runnerr<   E   s(     D)	 r(   r2   c               |    |g }t        | | dz  dz  fdt        | |      t        d d d d | 	      d
d      S )Nmemoryeventsc                      S Nr   )	snapshotss   r&   r6   z!_make_scheduler.<locals>.<lambda>]   s    ) r(   r=   c                    i S rB   r   )aes     r&   r6   z!_make_scheduler.<locals>.<lambda>`   s    2 r(   c                     y)N r   )rE   s    r&   r6   z!_make_scheduler.<locals>.<lambda>a   r7   r(   c                     y)Nr   r   ps    r&   r6   z!_make_scheduler.<locals>.<lambda>b   r7   r(   c                     y rB   r   rJ   s    r&   r6   z!_make_scheduler.<locals>.<lambda>c   r7   r(   )	gh_runner
git_runnerpytest_runneraudit_writertask_md_rootor)r8   decision_dirsnapshot_providerowner_triggermerge_executorownerrepo)r
   r<   r   )r;   rC   r2   s    ` r&   _make_schedulerrZ   R   sY     
(83+"8
C)%#%'!
  r(   c                    t               j                  t        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                  }d}d}||z  }d}||z   }	||	k(  }|st        j                  d|fd||	f      dt        j                         v st        j                  |       rt        j                  |       ndt        j                  |      t        j                  |      t        j                  |      t        j                  |      dz  }
dd|
iz  }t        t        j                  |            dx}x}x}x}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}}y)u   task-2563 §1: 29:59 (1799s) 은 pending_window (300s) 경과 + first_timeout (1800s) 미경과
    + reviews 0 → FIRST_TRIGGER_PENDING (이전엔 WITHIN_GRACE_PERIOD 였음, 회장 §명시 1:1 분리).r   r   z2026-05-12T10:29:59+00:00now==z-%(py2)s
{%(py2)s = %(py0)s.state
} == %(py4)sdiagr   py0py2py4assert %(py6)spy6N   <   ;   )z^%(py2)s
{%(py2)s = %(py0)s.elapsed_since_created_seconds
} == ((%(py5)s * %(py7)s) + %(py10)s))rd   re   py5py7py10zassert %(py13)spy13Fisz>%(py2)s
{%(py2)s = %(py0)s.requires_owner_trigger
} is %(py5)srd   re   rl   assert %(py7)srm   )r   diagnoser'   stater   
@pytest_ar_call_reprcompare@py_builtinslocals_should_repr_global_name	_safereprAssertionError_format_explanationelapsed_since_created_secondsrequires_owner_trigger)rb   @py_assert1@py_assert3@py_format5@py_format7@py_assert4@py_assert6@py_assert8@py_assert9@py_assert11@py_format12@py_format14@py_format6@py_format8s                 r&   8test_within_grace_29_59_classified_first_trigger_pendingr   n   s    %%89' & D ::4:44444:444444444444444:444444444444444444--==b=b=2=2=-====-======4===4===-======b===2========&&/%/&%////&%//////4///4///&///%///////r(   c                 n   t               j                  t        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}||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}}y)u9   30:00 정각, reviews 0 → FIRST_GEMINI_TRIGGER_MISSING.r   r\   z2026-05-12T10:30:00+00:00r]   r_   ra   rb   r   rc   rg   rh   N)zE%(py2)s
{%(py2)s = %(py0)s.elapsed_since_created_seconds
} == %(py4)sr   Trp   rr   rs   rt   rm   )z?%(py2)s
{%(py2)s = %(py0)s.latest_gemini_commit_id
} is %(py5)s)r   ru   r'   rv   r   rw   rx   ry   rz   r{   r|   r}   r~   r   r   r   latest_gemini_commit_id)rb   r   r   r   r   r   r   r   s           r&   2test_boundary_30_00_exact_classified_first_missingr   {   s   %%89' & D ::;:;;;;;:;;;;;;;4;;;4;;;:;;;;;;;;;;;;;;;;;;--F-1FFFFF-1FFFFFFF4FFF4FFF-FFFFFF1FFFF1FFFFFFFF&&.$.&$....&$......4...4...&...$.......''/4/'4////'4//////4///4///'///4///////r(   c                	   g }t        dd      }t        | |g|      }|j                  ddid      }|j                  }t	        |      }d	}||k(  }|s
t        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                  |      t        j                  |      dz  }	dd|	iz  }
t        t        j                  |
            d x}x}x}}|j                  d   }|j                  }|t        k(  }|st        j                  d
|fd|t        f      t        j                  |      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}x}}|j                  d   }|j                  }|t         k(  }|st        j                  d
|fd|t         f      t        j                  |      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}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   }ddi}||k(  }|slt        j                  d
|fd ||f      t        j                  |      t        j                  |      d!z  }d"d#|iz  }t        t        j                  |            d x}x}}y )$Nr   r   r   r    r=   OWNER_GEMINI_TRIGGER_TOKENghp_xxxxxxxxxxxxxxxxxxxxxxxxz2026-05-12T11:00:00+00:00envr^   r/   r_   )zP%(py5)s
{%(py5)s = %(py0)s(%(py3)s
{%(py3)s = %(py1)s.pr_actions
})
} == %(py8)slenresult)rd   py1py3rl   py8zassert %(py10)srn   r   z-%(py3)s
{%(py3)s = %(py1)s.state
} == %(py5)sr   r   r   rl   rt   rm   z.%(py3)s
{%(py3)s = %(py1)s.action
} == %(py5)sr   z0%(py3)s
{%(py3)s = %(py0)s(%(py1)s)
} == %(py6)sr2   rd   r   r   rh   assert %(py8)sr   r-   z/gemini review)z%(py1)s == %(py4)s)r   rf   rg   rh   )r'   rZ   run_one_cycle
pr_actionsr   rw   rx   ry   rz   r{   r|   r}   r~   rv   r   actionr   )r;   r2   snap	schedulerr   @py_assert2r   @py_assert7r   @py_format9@py_format11@py_assert0r   r   @py_assert5r   r   r   s                     r&   0test_past_1h_no_reviews_dispatches_owner_triggerr      s   J ;RHD4&ZHI$$)+IJ' % F   &3 !&Q&!Q&&&&!Q&&&&&&3&&&3&&&&&&v&&&v&&& &&&!&&&Q&&&&&&&QK%%K%)KKKKK%)KKKKKKK%KKKKKK)KKKK)KKKKKKKKQI&&I&*IIIII&*IIIIIII&IIIIII*IIII*IIIIIIIIz?a?a?a33zz?aa= >V-=$>> $>>>>> $>>>> >>>$>>>>>>>>r(   c                R   g }t        d      }t        | |g|      }|j                  ddid      }|j                  d   }|j                  }|t
        k(  }|st        j                  d	|fd
|t
        f      t        j                  |      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}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}}
y)u   task-2563 §1: 15:00 (900s) 은 pending_window 경과 → FIRST_TRIGGER_PENDING_SKIP.
    fast_path=false (default) 이므로 HTTP POST 호출 0.r   r\   r=   r   r   z2026-05-12T10:15:00+00:00r   r   r_   r   r   r   rt   rm   Nr   r   r2   r   r   r   )r'   rZ   r   r   r   r   rw   rx   r|   ry   rz   r{   r}   r~   r   r;   r2   r   r   r   r   r   r   r   r   r   r   r   s                r&   .test_within_grace_scheduler_skips_no_http_callr      sV    J ;<D4&ZHI$$)+IJ' % F
 QK&&K&*KKKKK&*KKKKKKK&KKKKKK*KKKK*KKKKKKKKz?a?a?a33zz?ar(   c                R   g }t        d      }t        | |g|      }|j                  ddid      }|j                  d   }|j                  }|t
        k(  }|st        j                  d	|fd
|t
        f      t        j                  |      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}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}}
y)uV   task-2563 §1: 200s (짧은 grace window 이내) → WITHIN_GRACE_PERIOD, HTTP POST 0.r   r\   r=   r   r   z2026-05-12T10:03:20+00:00r   r   r_   r   r   r   rt   rm   Nr   r   r2   r   r   r   )r'   rZ   r   r   r   r   rw   rx   r|   ry   rz   r{   r}   r~   r   r   s                r&   .test_within_grace_short_window_scheduler_skipsr      sT   J ;<D4&ZHI$$)+IJ' % F Q=&&=&*=====&*=======&======*====*========z?a?a?a33zz?ar(   c                B   g }t        t        d      }t        d|f      }t        | |g|      }|j	                  ddid	      }|j
                  d
   }|j                  }|t        k(  }|st        j                  d|fd|t        f      t        j                  |      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}x}}|j
                  d
   }|j                   }|t"        k(  }|st        j                  d|fd|t"        f      t        j                  |      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}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}}y)uN   30min 경과 + Gemini review (commit_id == head_sha) → GEMINI_FRESH_ON_HEAD.z2026-05-12T10:35:00+00:00)	commit_idsubmitted_atr   r   r=   r   r   z2026-05-12T10:36:00+00:00r   r   r_   r   r   r   rt   rm   Nr   r	   r   r   r2   r   r   r   )r   _HEADr'   rZ   r   r   rv   r   rw   rx   r|   ry   rz   r{   r}   r~   r   r	   r   )r;   r2   reviewr   r   r   r   r   r   r   r   r   r   r   s                 r&   5test_fresh_review_after_grace_period_classifies_freshr      s   J<WXF ;fYOD4&ZHI$$)+IJ' % F QC%%C%)CCCCC%)CCCCCCC%CCCCCC)CCCC)CCCCCCCCQ=&&=&*=====&*=======&======*====*========z?a?a?a33zz?ar(   c                    t         t        k(  } | st        j                  d| fdt         t        f      dt	        j
                         v st        j                  t               rt        j                  t               nddt	        j
                         v st        j                  t              rt        j                  t              nddz  }dd|iz  }t        t        j                  |            d} d	}d
}||z  }t         |k(  } | st        j                  d| fdt         |f      dt	        j
                         v st        j                  t               rt        j                  t               ndt        j                  |      t        j                  |      dz  }dd|iz  }t        t        j                  |            dx} x}x}}y)u   task-2563 §1 1:1: GRACE_PERIOD_SECONDS 는 polling_policy.FIRST_TIMEOUT_SECONDS 와 일치 (1800s).
    회장 §명시 "FIRST_TIMEOUT_SECONDS=1800 1:1" 박제.r_   )z%(py0)s == %(py2)sr   r   )rd   re   zassert %(py4)srf   N   rj   z%(py0)s == (%(py3)s * %(py5)s)rd   r   rl   r   r   )
r   r   rw   rx   ry   rz   r{   r|   r}   r~   )r   @py_format3r   r   r   r   r   r   s           r&   0test_grace_period_constant_matches_first_timeoutr      s      #88888#8888888888888888#8888#88888888#%**27*7****7************2**********r(   c                    d} d}| |z  }t         |k(  }|st        j                  d|fdt         |f      dt        j                         v st        j
                  t               rt        j                  t               ndt        j                  |       t        j                  |      dz  }dd|iz  }t        t        j                  |            d	x}x} x}}y	)
u>   task-2563 §1: "PR open 직후 짧은 시간" = 5 분 default.   rj   r_   r   r   r   r   r   N)	r   rw   rx   ry   rz   r{   r|   r}   r~   )r   r   r   r   r   r   s         r&   6test_first_trigger_pending_window_default_is_5_minutesr      sx    349r91r69/69999/6999999/999/9991999r9999999r(   )r   intr   strr   r   r    ztuple[GeminiReviewMeta, ...]r!   boolreturnr   )r;   r   r2   listr   r   )r;   r   rC   zlist[IdlePRSnapshot]r2   zlist | Noner   r
   )7__doc__
__future__r   builtinsry   _pytest.assertion.rewrite	assertionrewriterw   syspathlibr   __file__resolveparentsWORKSPACE_ROOTr   r,   insertanu_v2.executor_schedulerr   r   r   r	   r
   anu_v2.idle_pr_diagnoserr   r   r   r   r   r   r   r   r   anu_v2.polling_policyr   anu_v2.merge_queue_executorr   anu_v2.owner_trigger_auditr   anu_v2.owner_trigger_onlyr   r   r'   r<   rZ   r   r   r   r   r   r   r   r   r   r(   r&   <module>r      s9   #   
  h'')11!4~chh&HHOOAs>*+ 
 
 
 8 : 8 6 	
 1,.  	
 *  $
" #	# 	
 8
0	0? 
  +:r(   