
    9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 ddlZ ee      j                         j                  d   Z ee      e	j$                  vr"e	j$                  j'                  d ee             ddlmZ ddlmZ ddlmZ dd	lmZ dd
lmZmZmZ ddl m!Z!m"Z"m#Z#m$Z$m%Z% d Z&ddZ'd Z(d Z)d Z*ejV                  jY                  dg d      d        Z-d Z.d Z/d Z0d Z1d Z2d Z3d Z4d Z5y)u#  task-2556 §11 — OWNER token 부재 시 TokenBoundaryViolation 회귀.

회장 §명시 2026-05-12 §11:
  "token boundary 검증 — BOT_GITHUB_TOKEN 으로 /gemini review 호출 차단 +
   OWNER token actor 검증"

검증 포인트:
  1. env 가 None 이면 TokenBoundaryViolation.
  2. env 에 OWNER_GEMINI_TRIGGER_TOKEN 누락이면 TokenBoundaryViolation.
  3. env 에 BOT_GITHUB_TOKEN 이 있으면 TokenBoundaryViolation.
  4. env 에 GH_TOKEN/GITHUB_TOKEN/OWNER_PAT/PAT_TOKEN 어느 것이라도 있으면 violation.
  5. 빈 문자열 token 도 violation.
  6. fixture (executor_token_unavailable.json) 결과 어셀션.
  7. token 부재 시 http_post 호출 0, decision.json 미생성.
  8. 또한 polling_policy long polling 게이트 통합 — bot session 종료 후 재진입 확인.
    )annotationsN)Path   )ExecutorSchedulerIdlePRSnapshot)MergeQueueExecutor)OwnerTriggerAudit)OwnerTriggerOnlyTokenBoundaryViolationassert_scheduler_token_boundary)BotSessionExitRequiredPollingStateMAX_RECHECKSadvance_recheckmust_exit_nowc                 $    t        dddddd      S )N   (aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaztask/task-2556-dev5z2026-05-12T10:00:00+00:00 T)numberhead_shahead_ref
created_atgemini_reviewsci_required_all_successr   r       C/home/jay/workspace/anu_v2/tests/test_executor_token_unavailable.py_snapr   0   s!    &. $ r   c                    g fd}t        | |d t        |             }t        | | dz  dz  d |t        d d d	 d
 |       dd      }|fS )Nc                4    j                  ||d       ddiS )N)pathbodyid   )append)methodr"   r#   headers
http_callss       r   	http_postz"_make_scheduler.<locals>.http_post>   s     467ayr   c                      y)N"ghp_x_xxxxxxxxxxxxxxxxxxxxxxxxxxxxr   r   r   r   <lambda>z!_make_scheduler.<locals>.<lambda>E       r   )workspace_rootr*   token_providerauditmemoryeventsc                     t               gS N)r   r   r   r   r-   z!_make_scheduler.<locals>.<lambda>K   s
    57) r   c                    i S r5   r   )aes     r   r-   z!_make_scheduler.<locals>.<lambda>N   s    2 r   c                     y)N r   )r7   s    r   r-   z!_make_scheduler.<locals>.<lambda>O   r.   r   c                     y)Nr   r   ps    r   r-   z!_make_scheduler.<locals>.<lambda>P   r.   r   c                     y r5   r   r<   s    r   r-   z!_make_scheduler.<locals>.<lambda>Q   r.   r   )	gh_runner
git_runnerpytest_runneraudit_writertask_md_rootor)r/   decision_dirsnapshot_providerowner_triggermerge_executorownerrepo)r   r
   r   r	   )tmp_pathr*   runner	schedulerr)   s       @r   _make_schedulerrO   ;   sw    J C)	F "(83+)%#%'!
 I j  r   c                 v    t        j                  t              5  t        d        d d d        y # 1 sw Y   y xY wr5   )pytestraisesr   r   r   r   r   -test_env_none_raises_token_boundary_violationrS   Y   s,    	-	. .'-. . .s   /8c                    t        j                  t              5 } t        i        d d d        d} j                  }t        |      }||v }|s
t        j                  d|fd||f      t        j                  |      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}x}}y # 1 sw Y   =xY w)	NOWNER_GEMINI_TRIGGER_TOKENinzK%(py1)s in %(py8)s
{%(py8)s = %(py3)s(%(py6)s
{%(py6)s = %(py4)s.value
})
}strexcpy1py3py4py6py8assert %(py10)spy10rQ   rR   r   r   valuerY   
@pytest_ar_call_reprcompare	_saferepr@py_builtinslocals_should_repr_global_nameAssertionError_format_explanationrZ   @py_assert0@py_assert5@py_assert7@py_assert2@py_format9@py_format11s          r   -test_env_missing_owner_token_raises_violationrt   ^   s    	-	. ,#'+,'9syy93y>9'>9999'>999'99999939993999999s999s999y999>9999999, ,s   E!!E+c                    t        j                  t              5 } t        ddd       d d d        d} j                  }t        |      }||v }|s
t        j                  d|fd||f      t        j                  |      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}x}}y # 1 sw Y   =xY w)Nghp_bot_xxxxghp_owner_xxxxBOT_GITHUB_TOKENrU   ry   rV   rX   rY   rZ   r[   ra   rb   rc   rm   s          r   +test_env_bot_token_present_raises_violationrz   d   s    	-	. #' .*:)
 	
 /SYY/Y///////////////////////S///S///Y////////// s   E$$E.forbidden_env_name)ry   GH_TOKENGITHUB_TOKEN	OWNER_PAT	PAT_TOKENc                6   t        j                  t              5 }t        | dddi       d d d        j                  }t        |      }| |v }|s7t        j                  d|fd| |f      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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 # 1 sw Y   fxY w)NxrU   rw   rV   )zK%(py0)s in %(py7)s
{%(py7)s = %(py2)s(%(py5)s
{%(py5)s = %(py3)s.value
})
}r{   rY   rZ   )py0py2r]   py5py7zassert %(py9)spy9)rQ   rR   r   r   rd   rY   re   rf   rh   ri   rj   rg   rk   rl   )r{   rZ   @py_assert4@py_assert6@py_assert1@py_format8@py_format10s          r   &test_all_forbidden_token_names_blockedr   m   s    
 
-	. #'(*:)
 	
 &)YY/Y/////////////////////////////S///S///Y////////// s   FFc            
     ^   t        j                  t              5 } t        ddi       d d d        d} j                  }t        |      }|j                  } |       }||v }|s2t        j                  d|fd||f      t        j                  |      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                  |      t        j                  |      dz  }d	d
|iz  }t        t        j                  |            d x}x}x}x}x}}y # 1 sw Y   |xY w)NrU   r:   emptyrV   )z%(py1)s in %(py12)s
{%(py12)s = %(py10)s
{%(py10)s = %(py8)s
{%(py8)s = %(py3)s(%(py6)s
{%(py6)s = %(py4)s.value
})
}.lower
}()
}rY   rZ   )r\   r]   r^   r_   r`   rb   py12zassert %(py14)spy14)rQ   rR   r   r   rd   rY   lowerre   rf   rg   rh   ri   rj   rk   rl   )	rZ   rn   ro   rp   @py_assert9@py_assert11rq   @py_format13@py_format15s	            r   (test_empty_string_token_raises_violationr   z   s	   	-	. L#')Er(JKL,#)),c)n,n**,*,,7,,,,,7,,,,7,,,,,,c,,,c,,,,,,#,,,#,,,),,,n,,,*,,,,,,,,,,,,L Ls   F""F,c                0   t        |       \  }}t        j                  t              5  |j	                  i d       d d d        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z  dz  dz  }|j                  }	 |	       }
|
 }|sddt        j                         v st        j                  |      rt        j                  |      ndt        j                  |	      t        j                  |
      dz  }t        t        j                  |            d x}	x}
}y # 1 sw Y   xY w)N2026-05-12T11:00:00+00:00envnowr   ==z0%(py3)s
{%(py3)s = %(py0)s(%(py1)s)
} == %(py6)slenr)   r   r\   r]   r_   assert %(py8)sr`   r2   r3   z%task-2556.owner_trigger_decision.jsonzEassert not %(py4)s
{%(py4)s = %(py2)s
{%(py2)s = %(py0)s.exists
}()
}decision_path)r   r   r^   )rO   rQ   rR   r   run_one_cycler   re   rf   rh   ri   rj   rg   rk   rl   exists)rL   rN   r)   rq   ro   r   @py_format7rr   r   r   @py_assert3@py_format6s               r   *test_run_one_cycle_blocks_when_env_missingr      sU   +H5Iz	-	. IB,GHI z?a?a?a33zz?ax'(25\\M##%#%%%%%%%%%%%}%%%}%%%#%%%%%%%%%%I Is   HHc                   t        |       \  }}t        j                  t              5  |j	                  dddd       d d d        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 # 1 sw Y   xY w)Nrv   rw   rx   r   r   r   r   r   r   r)   r   r   r`   )rO   rQ   rR   r   r   r   re   rf   rh   ri   rj   rg   rk   rl   )rL   rN   r)   rq   ro   r   r   rr   s           r   1test_run_one_cycle_blocks_when_bot_token_injectedr      s    +H5Iz	-	. 
$2.> , 	  	

 z?a?a?a33zz?a
 
s   EE"c                    dd l } t        t              j                         j                  d   dz  dz  }| j                  |j                  d            }|d   D ]  }|d   }t        j                  t              5 }t        |       d d d        |d	   }|j                  } |       }j                  }	t        |	      }
|
j                  } |       }||v }|st        j                  d
|fd||f      t        j                   |      t        j                   |      t        j                   |      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                   |      t        j                   |      d	z  }t        j(                  d|d    d|d	   d|j                        dz   d|iz  }t+        t        j,                  |            d x}x}x}x}x}	x}
x}} y # 1 sw Y   xY w)Nr   r%   fixtureszexecutor_token_unavailable.jsonzutf-8)encoding	env_casesr   expected_message_containsrV   )z%(py5)s
{%(py5)s = %(py3)s
{%(py3)s = %(py1)s.lower
}()
} in %(py16)s
{%(py16)s = %(py14)s
{%(py14)s = %(py12)s
{%(py12)s = %(py7)s(%(py10)s
{%(py10)s = %(py8)s.value
})
}.lower
}()
}rY   rZ   )	r\   r]   r   r   r`   rb   r   r   py16zcase=labelz: expected z in z
>assert %(py18)spy18)jsonr   __file__resolveparentsloads	read_textrQ   rR   r   r   r   rd   rY   re   rf   rg   rh   ri   rj   _format_assertmsgrk   rl   )r   fixture_pathfixturecaser   rZ   rn   rq   r   r   r   @py_assert13@py_assert15r   @py_format17@py_format19s                   r   .test_token_unavailable_fixture_cases_all_raiser      s   X ((+j8;\\  jj///ABG$ d5k]]12 	1c+C0	1 ,-	d-33	d35	d=@YY	d9<Y	d9G9M9M	d9M9O	d59OO	d 	dRcRc	d59O	d 	dZcZc .	d 	dZcZc 4	d 	dZcZc 6	d 	d]c]c	d 	dKcKc :=	d 	dZcZc :=	d 	d]c]c	d 	dKcKc >A	d 	dZcZc >A	d 	dZcZc >G	d 	dZcZc :H	d 	dZcZc :N	d 	dZcZc :P	d 	dRcRcU4=/T2M-N,QQUVYV_V_Ubc	d 	d 	dPcPc	d 	d 	d 	d	d	1 	1s   =I77J	c                 6   t        dd      } t        |       }|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                  t              5  t        |       ddd       y# 1 sw Y   yxY w)uI   recheck 1 회 후 즉시 봇 종료, 다음 cycle 은 외부 cron 책임.r   iX  rechecks_doneelapsed_secondsr%   r   )z5%(py2)s
{%(py2)s = %(py0)s.rechecks_done
} == %(py5)sstate_after_first)r   r   r   zassert %(py7)sr   N)r   r   r   re   rf   rh   ri   rj   rg   rk   rl   rQ   rR   r   )stater   r   r   r   r   r   s          r   1test_bot_session_exit_required_after_max_rechecksr      s    q#>E'.**/a/*a////*a////////////*///a///////	-	. +)*+ + +s   :DDc                 D   t        t        d      } t        |       }d}||u }|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 )Nr   r   T)is)z0%(py3)s
{%(py3)s = %(py0)s(%(py1)s)
} is %(py6)sr   r   r   r   r`   )r   r   r   re   rf   rh   ri   rj   rg   rk   rl   )r   rq   ro   r   r   rr   s         r   'test_must_exit_now_at_max_rechecks_truer      s    |QGE'4'4''''4''''''='''='''''''''''''''4'''''''r   c                   t        |       \  }}t        t        d      }t        j                  t
              5  |j                  ddid|       d d d        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 # 1 sw Y   xY w)Nr   r   rU   ghp_x_xxxxxxxxxxxxxxxxxxxxr   )r   r   cycle_polling_stater   r   r   r)   r   r   r`   )rO   r   r   rQ   rR   r   r   r   re   rf   rh   ri   rj   rg   rk   rl   )	rL   rN   r)   
exit_staterq   ro   r   r   rr   s	            r   4test_run_one_cycle_raises_when_already_at_exit_stater      s    +H5IzL!LJ	-	. 
-/KL+ * 	  	

 z?a?a?a33zz?a
 
s   E))E3c                   t        |       \  }}|j                  ddid       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did      }|j                  d   }	|	j                  }d}||k(  }|st        j                  d|fd||f      t        j                  |	      t        j                  |      t        j                  |      dz  }dd|iz  }t        t        j                  |            dx}	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   state persisted markers 기반 재진입 확인 (회장 §12).

    1st cycle 종료 후 audit JSONL + decision.json + POSTED marker 가 박제되어
    2nd cycle (외부 cron) 이 동일 (pr, head) 를 보면 SAME_HEAD_DEDUPED 로 fail-closed.
    rU   r   r   r   r%   r   r   r   r)   r   r   r`   Nz2026-05-12T11:05:00+00:00r   SAME_HEAD_DEDUPED)z.%(py3)s
{%(py3)s = %(py1)s.action
} == %(py6)s)r\   r]   r_   )rO   r   r   re   rf   rh   ri   rj   rg   rk   rl   
pr_actionsaction)
rL   rN   r)   rq   ro   r   r   rr   result2rn   s
             r   3test_scheduler_audit_persists_for_next_cycle_resumer      s    ,H5Iz)+GH'   z?a?a?a33zz?a%%)+GH' & G a > ''>+>>'+>>>>>'+>>>> >>>'>>>+>>>>>>>>z?a?a?a33zz?ar   )rL   r   returnztuple[ExecutorScheduler, list])6__doc__
__future__r   builtinsrh   _pytest.assertion.rewrite	assertionrewritere   syspathlibr   rQ   r   r   r   WORKSPACE_ROOTrY   r"   insertanu_v2.executor_schedulerr   anu_v2.idle_pr_diagnoserr   anu_v2.merge_queue_executorr	   anu_v2.owner_trigger_auditr
   anu_v2.owner_trigger_onlyr   r   r   anu_v2.polling_policyr   r   r   r   r   r   rO   rS   rt   rz   markparametrizer   r   r   r   r   r   r   r   r   r   r   r   <module>r      s   " #   
   h'')11!4~chh&HHOOAs>*+ 7 3 : 8 
 !<.
:0 N0	0-&
 d$+(
	  r   