
    w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ddlmZ ddlmZ ddlmZ dZd	Zed
ddZddZd Zd Zd Zd Zy)u  task-2554+2 §5 신규 fixture #4: PR head 변경 감지 시 즉시 FAILED + http_post 미발생.

회장 §명시 (2026-05-12): runner 호출 후 PR head 가 decision.current_head 와 다르면
``DecisionInvalidError`` (E_HEAD_MISMATCH) 즉시 발생 — http_post 0회.
    )annotationsN)Path)OwnerTriggerAudit)DecisionInvalidError)OwnerTriggerOnly(aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa(bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbheadc               x    ddd|dddddddd	}| d
z  }|j                  t        j                  |      d       |S )Nz anu_v2.owner_trigger_decision.v1ztask-2554+2-testi   TFr   "POST_GEMINI_REVIEW_TRIGGER_COMMENTz/gemini review)schematask_idprcurrent_head
queue_headcurrent_head_confirmedgemini_evidence_freshnudge_count_for_pr_headallowed_actioncomment_bodyallowedzdecision.jsonzutf-8)encoding)
write_textjsondumps)tmp_pathr   decisionps       O/home/jay/workspace/anu_v2/tests/test_owner_trigger_head_changed_fail_closed.py_write_decisionr"      sT    4%"&!&#$>(H 	?"ALLH%L8H    c                R    g t        |       }fd}t        | |d |      }||fS )Nc                H    j                  | |t        |      d       ddiS )N)methodpathbodystatus   )appenddict)r&   r'   r(   headerspostss       r!   	http_postz _build_module.<locals>.http_post.   s$    d4jIJ#r#   c                      yNzowner-token r2   r#   r!   <lambda>z_build_module.<locals>.<lambda>5       r#   workspace_rootr/   token_provideraudit)r   r   )r   r8   r/   modr.   s       @r!   _build_moduler:   *   s>    Eh'E ,	C ur#   c                   t        | t              }t        |       \  }}}t        j                  t
              5 }|j                  |ddt               ddd       j                  }|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                  |      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# 1 sw Y   xY w)ub   decision.current_head=_HEAD_A 인데 actual head=_HEAD_B → DecisionInvalidError E_HEAD_MISMATCH.r
   ordecision_pathownerrepocurrent_head_actualNE_HEAD_MISMATCH==zG%(py4)s
{%(py4)s = %(py2)s
{%(py2)s = %(py0)s.value
}.code
} == %(py7)sexc_infopy0py2py4py7assert %(py9)spy9r   z0%(py3)s
{%(py3)s = %(py0)s(%(py1)s)
} == %(py6)slenr.   rI   py1py3py6assert %(py8)spy8)r"   _HEAD_Ar:   pytestraisesr   trigger_gemini_review_HEAD_Bvaluecode
@pytest_ar_call_reprcompare@py_builtinslocals_should_repr_global_name	_safereprAssertionError_format_explanationrP   )r   r?   r9   r.   _rG   @py_assert1@py_assert3@py_assert6@py_assert5@py_format8@py_format10@py_assert2@py_assert4@py_format7@py_format9s                   r!   <test_decision_head_mismatch_actual_head_raises_head_mismatchrq   ;   sx   #H7;M!(+MC	+	, 
!!' '	 	" 	

 >>3>3"33"33333"333333383338333>333333"33333333u::?:33uu:
 
s   II!c                P   t        | t              }t        |       \  }}}t        j                  t
              5  |j                  |ddt               ddd       t        |j                               }g }||k(  }|st        j                  d|fd||f      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}}y# 1 sw Y   xY w)uR   head mismatch 는 transaction 진입 전에 차단 → audit 에 PENDING 기록 0.r
   r<   r=   r>   NrD   )z%(py0)s == %(py3)srows)rI   rS   zassert %(py5)spy5)r"   rW   r:   rX   rY   r   rZ   r[   list
_iter_rowsr^   r_   r`   ra   rb   rc   rd   re   )
r   r?   r9   rf   r8   rs   rm   rg   @py_format4@py_format6s
             r!   3test_head_mismatch_does_not_record_pending_in_auditry   K   s    #H7;M!(+MCE	+	, 
!!' '	 	" 	

   "#D42:42442
 
s   DD%c                   t        | t              }t        |       \  }}}t        j                  t
              5 }|j                  |ddd       ddd       j                  }|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                  |      t        j                  |      dz  }	dd|	iz  }
t        t        j                   |
            dx}x}x}}y# 1 sw Y   xY w)u;   actual head 가 40-char hex 아니면 E_ACTUAL_HEAD_FORMAT.r
   r<   r=   z
short-headr>   NE_ACTUAL_HEAD_FORMATrD   rF   rG   rH   rM   rN   )r"   rW   r:   rX   rY   r   rZ   r\   r]   r^   r_   r`   ra   rb   rc   rd   re   )r   r?   r9   rf   rG   rg   rh   ri   rj   rk   rl   s              r!   &test_invalid_actual_head_format_raisesr|   Z   s    #H7;Mh'ICA	+	, 
!!' ,	 	" 	

 >>8>8"88"88888"888888888888888>888888"88888888
 
s   EEc                X   t        | t              }t        |       } G d dt              fd}t	        | |d |      }t        j                        5  |j                  |ddt        	       d
d
d
       t        |j                               }|d   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}}g fd}t	        | |d |      }|j                  |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'              }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) u   sequence: 1st call with head_A → PENDING(crash sim) → 2nd call with same head_A is dedupe.

    head 변경 (different actual head) 인 경우는 위 head_mismatch 로 처리.
    r
   c                      e Zd Zy)[test_head_changed_subsequent_call_does_not_post_if_already_pending.<locals>._SimulatedCrashN)__name__
__module____qualname__r2   r#   r!   _SimulatedCrashr   p   s    r#   r   c                     d      )Nzpost-PENDING crashr2   )r&   r'   r(   r-   r   s       r!   crash_http_postz[test_head_changed_subsequent_call_does_not_post_if_already_pending.<locals>.crash_http_posts   s    233r#   c                      yr1   r2   r2   r#   r!   r3   zTtest_head_changed_subsequent_call_does_not_post_if_already_pending.<locals>.<lambda>y   r4   r#   r5   r<   r=   r>   Nr   resultPENDINGrD   )z%(py1)s == %(py4)s)rR   rK   zassert %(py6)srT   c                2    j                  d| i       ddiS )Nr&   r)   r*   )r+   )r&   r'   r(   r-   posts2s       r!   r/   zUtest_head_changed_subsequent_call_does_not_post_if_already_pending.<locals>.http_post   s    x()#r#   c                      yr1   r2   r2   r#   r!   r3   zTtest_head_changed_subsequent_call_does_not_post_if_already_pending.<locals>.<lambda>   r4   r#   DEDUPED)z.%(py2)s
{%(py2)s = %(py0)s.status
} == %(py5)s)rI   rJ   rt   zassert %(py7)srL   rO   rP   r   rQ   rU   rV   )r"   rW   r   
SystemExitr   rX   rY   rZ   ru   rv   r^   r_   rc   rd   re   r)   r`   ra   rb   rP   )r   r?   r8   r   crashing_modafter_crash@py_assert0rh   rm   @py_format5ro   r/   next_runnerr=   rg   rn   rx   rk   rj   rp   r   r   s                       @@r!   Btest_head_changed_subsequent_call_does_not_post_if_already_pendingr   h   s1   
 $H7;Mh'E* 4 $!,	L 
	' 
**' '	 	+ 	

 u'')*Kq>(#0y0#y0000#y000#000y0000000F #,	K 	))##	 	* 	A 88 y 8y    8y      1   1   8   y       v;!;!;!33vv;!=
 
s   LL))r   r   r   strreturnr   )r   r   )__doc__
__future__r   builtinsr`   _pytest.assertion.rewrite	assertionrewriter^   r   pathlibr   rX   anu_v2.owner_trigger_auditr   anu_v2.owner_trigger_decisionr   anu_v2.owner_trigger_onlyr   rW   r[   r"   r:   rq   ry   r|   r   r2   r#   r!   <module>r      sT    #      8 > 6 
 4; &" 92r#   