
    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mZ ddlZddl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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*  ee+      jX                  jX                  dz  d	z  Z- G d
 d      Z.ddZ/ddZ0 G d d      Z1 G d d      Z2 G d d      Z3 G d d      Z4y)u2  anu_v2.tests.test_gemini_second_review_bottleneck_2565 — task-2565 Phase 1+2 테스트.

Phase 1: Schema + Policy 상수 박제 테스트 (6 종).
Phase 2: Stale detection + owner trigger 자동 호출 테스트 (5 종).

회장 §명시 task-2565 §5 Phase 1 Test 요구사항 1:1 박제:
  - test_second_review_decision_schema_validation
  - test_pre_merge_commit_decision_schema_validation
  - test_state_enum_serialization
  - test_long_polling_forbidden_constant
  - test_grace_default_180s
  - test_max_recheck_default_1

Phase 2 테스트 (task-2565 §5 Phase 2):
  - test_follow_up_commit_marks_gemini_stale_on_head
  - test_second_review_owner_trigger_called_after_grace
  - test_same_head_duplicate_owner_trigger_deduped
  - test_grace_not_elapsed_returns_pending
  - test_phase2_markers_emitted_correctly
    )annotationsN)Path)	MagicMock)LONG_POLLING_FORBIDDENMAX_SECOND_REVIEW_RECHECKSSECOND_REVIEW_GRACE_SECONDS)MAX_CI_RERUN_PER_HEADCIRerunInputMergeReadyInputPreMergeCommitInputSchemaViolationSecondReviewInputSecondReviewStateauto_rerun_failed_ci_jobsauto_trigger_owner_reviewbuild_pre_merge_block_decisionbuild_pre_merge_commit_decisionbuild_second_review_decisionclassify_pre_merge_commitdetermine_stateemit_phase2_markers&is_gemini_stale_on_head_after_followupis_merge_readyis_owner_trigger_deduped$post_merge_evidence_redirect_payloadshould_rerun_failed_ci_jobs"validate_pre_merge_commit_decisionvalidate_second_review_decisionwrite_markerfixturesgemini_second_review_bottleneckc                  P    e Zd ZdZddZddZddZddZddZddZ	ddZ
dd	Zy
)TestPhase1SchemaAndPolicyu;   Phase 1 — Schema 검증 + 정책 상수 박제 테스트.c                2    t        dddddddddd	d
dd      S )u-   정상 second_review_decision.v1 dict 반환.	task-2565k   d251399ccee55afeF#SHA_MISMATCH_AFTER_FOLLOW_UP_COMMITGEMINI_EVIDENCE_STALETPOSTEDNWAITINGtask_id	pr_numberold_head_shacurrent_head_shalatest_gemini_commit_idgemini_fresh_on_current_headstale_reasonci_gate_reasonowner_trigger_requiredowner_trigger_resultfresh_detected_atci_rerun_requiredfinal_decision)r   selfs    X/home/jay/workspace/scripts/../anu_v2/tests/test_gemini_second_review_bottleneck_2565.py_valid_second_review_decisionz7TestPhase1SchemaAndPolicy._valid_second_review_decisionK   s6    +#'$.).>2#'!)""$
 	
    c                $    t        ddgddd      S )u0   정상 pre_merge_commit_decision.v1 dict 반환.T anu_v2/second_review_recovery.pyreport_onlyF)merge_readychanged_fileschange_typepre_merge_commit_allowedredirect_to_post_merge_evidence)r   r;   s    r=    _valid_pre_merge_commit_decisionz:TestPhase1SchemaAndPolicy._valid_pre_merge_commit_decision]   s!    .=>%%*,0
 	
r?   c                   | j                         }t        |       t        |      }d|d<   t        j                  t
        d      5  t        |       ddd       t        |      }d|d<   t        j                  t
        d      5  t        |       ddd       t        |      }|d= t        j                  t
        d      5  t        |       ddd       t        |      }d|d	<   t        j                  t
        d	      5  t        |       ddd       y# 1 sw Y   xY w# 1 sw Y   xY w# 1 sw Y   axY w# 1 sw Y   yxY w)
u  second_review_decision.v1 schema 검증 5 종 케이스 (task-2565 §5 Phase1 Test).

        케이스:
          1. 정상 decision → validate 통과 (SchemaViolation raise 없음)
          2. manual_owner_input_requested=True → SchemaViolation
          3. long_polling_used=True → SchemaViolation
          4. 필수 필드 누락 → SchemaViolation
          5. 잘못된 final_decision enum → SchemaViolation
        Tmanual_owner_input_requestedmatchNlong_polling_usedr.      누락 필드INVALID_DECISION_VALUEr:   )r>   r   dictpytestraisesr   )r<   decision
bad_manualbad_pollingmissing_fieldbad_enums         r=   -test_second_review_decision_schema_validationzGTestPhase1SchemaAndPolicy.test_second_review_decision_schema_validationi   s#    557'1 (^
59
12]]?2PQ 	8+J7	8 8n+/'(]]?2EF 	9+K8	9 X)$]]?/B 	;+M:	; >%=!"]]?2BC 	6+H5	6 	6%	8 	8	9 	9	; 	;	6 	6s0   DD#D/D;D #D,/D8;Ec                b   | j                         }t        |       t        |      }d|d<   t        j                  t
        d      5  t        |       ddd       t        |      }|d= t        j                  t
        d      5  t        |       ddd       y# 1 sw Y   GxY w# 1 sw Y   yxY w)u1  pre_merge_commit_decision.v1 schema 검증 3 종 케이스 (task-2565 §5 Phase1 Test).

        케이스:
          1. 정상 decision → validate 통과 (SchemaViolation raise 없음)
          2. 잘못된 change_type → SchemaViolation
          3. 필수 필드 누락 → SchemaViolation
        INVALID_CHANGE_TYPErE   rK   NrC   rN   )rH   r   rP   rQ   rR   r   )r<   rS   bad_typerV   s       r=   0test_pre_merge_commit_decision_schema_validationzJTestPhase1SchemaAndPolicy.test_pre_merge_commit_decision_schema_validation   s     88:*84 >"7]]?-@ 	9.x8	9 X-(]]?/B 	>.}=	> 	>	9 	9	> 	>s   BB%B"%B.c           	        h d}t         D ch c]  }|j                   }}||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  }t        j                  d||z
   d||z
         d	z   d
|iz  }t        t        j                  |            d}t         D ]3  }|j                  }t        |t              }	|	sWt        j                  |j                   dt        |j                               dz   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t        j                  |	      dz  }
t        t        j                  |
            dx}}	|j                  }|st        j                  |j                   d      dz   dt	        j
                         v st        j                  |      rt        j                  |      ndt        j                  |      dz  }t        t        j                  |            d}t        |j                        }||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  }t        j                  d|j                  d|       d	z   d
|iz  }t        t        j                  |            d}6 yc c}w )u   9 상태 모두 string 변환 + 역변환 가능 (task-2565 §2).

        SecondReviewState 9 개 전수:
          - .value 가 non-empty string
          - SecondReviewState(value) 역변환 == 원본 enum
        >	   SECOND_REVIEW_PENDINGSECOND_REVIEW_TIMEOUTFOLLOW_UP_COMMIT_CREATEDSECOND_REVIEW_FRESH_DETECTEDCI_RERUN_REQUIRED_AFTER_FRESHGEMINI_EVIDENCE_STALE_ON_HEADSECOND_REVIEW_TRIGGER_REQUIREDMERGE_READY_AFTER_SECOND_REVIEW"SECOND_REVIEW_OWNER_TRIGGER_POSTED==)z%(py0)s == %(py2)sactual_valuesexpected_states)py0py2u   상태 enum 불일치: 누락=u	   , 추가=z
>assert %(py4)spy4Nu   .value 가 str 이 아님: zR
>assert %(py6)s
{%(py6)s = %(py0)s(%(py3)s
{%(py3)s = %(py1)s.value
}, %(py4)s)
}
isinstancestatestr)rk   py1py3rm   py6u   .value 가 빈 stringz+
>assert %(py2)s
{%(py2)s = %(py0)s.value
}	roundtripu$   역변환 실패: SecondReviewState(z) != )r   value
@pytest_ar_call_reprcompare@py_builtinslocals_should_repr_global_name	_saferepr_format_assertmsgAssertionError_format_explanationrn   rp   nametype)r<   rj   sri   @py_assert1@py_format3@py_format5ro   @py_assert2@py_assert5@py_format7rt   s               r=   test_state_enum_serializationz7TestPhase1SchemaAndPolicy.test_state_enum_serialization   su   

 +<<Q<</ 	
 	
} 	
 	
 
6	
 	
   	
 	
 
	  	
 	
 
6	
 	
  !0 	
 	
 
	 !0 	
 	
  -_}-L,M N#o568	
 	
 	
 	
 	
 ' 	E#kk :k3/ /   ::,9$u{{:K9LM v     I   v   $  I $  I *  v   ,/  I ,/  I 0      ;;D;DD5::,.C DDDDDDD5DDD5DDD;DDDDDD)%++6I%  9  v     I   v   !&  I !&    7u{{oU5'R    	 =s   Qc                   d}t         |u }|st        j                  d|fdt         |f      dt        j                         v st        j
                  t               rt        j                  t               ndt        j                  |      dz  }t        j                  d      dz   d|iz  }t        t        j                  |            d	x}}y	)
u   LONG_POLLING_FORBIDDEN is True (task-2565 §3, §8 #4).

        long polling 금지 상수가 polling_policy.py 에 정확히 True 로 박제되어 있음을 검증.
        Tisz%(py0)s is %(py3)sr   rk   rr   uR   LONG_POLLING_FORBIDDEN must be True — long polling 금지 박제 (task-2565 §3)
>assert %(py5)spy5N)
r   rv   rw   rx   ry   rz   r{   r|   r}   r~   r<   r   r   @py_format4@py_format6s        r=   $test_long_polling_forbidden_constantz>TestPhase1SchemaAndPolicy.test_long_polling_forbidden_constant   s    
 *. 	
%- 	
 	
% 	
 	
	6	
 	
  & 	
 	
 		 & 	
 	
 		 *. 	
 	
  a	
 	
 	
 	
 	
r?   c                   d}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                  |      dz  }t        j                  dt                dz   d|iz  }t        t        j                  |            d	x}}y	)
u   SECOND_REVIEW_GRACE_SECONDS == 180 (task-2565 §3, 회장 결정 180초).

        follow-up commit 후 fresh evidence 대기 grace 구간이 180초로 박제됨.
           rg   z%(py0)s == %(py3)sr   r   z-SECOND_REVIEW_GRACE_SECONDS must be 180, got r   r   N)
r   rv   rw   rx   ry   rz   r{   r|   r}   r~   r   s        r=   test_grace_default_180sz1TestPhase1SchemaAndPolicy.test_grace_default_180s   s    
 /2 	
*c1 	
 	
*c 	
 	
	6	
 	
  + 	
 	
 		 + 	
 	
 		 /2 	
 	
  <<W;XY	
 	
 	
 	
 	
r?   c                   d}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                  |      dz  }t        j                  dt                dz   d|iz  }t        t        j                  |            d	x}}y	)
u   MAX_SECOND_REVIEW_RECHECKS == 1 (task-2565 §3).

        owner_trigger 호출 후 재확인 횟수가 max 1 회로 박제됨.
           rg   r   r   r   z*MAX_SECOND_REVIEW_RECHECKS must be 1, got r   r   N)
r   rv   rw   rx   ry   rz   r{   r|   r}   r~   r   s        r=   test_max_recheck_default_1z4TestPhase1SchemaAndPolicy.test_max_recheck_default_1   s    
 ./ 	
)Q. 	
 	
)Q 	
 	
	6	
 	
  * 	
 	
 		 * 	
 	
 		 ./ 	
 	
  99S8TU	
 	
 	
 	
 	
r?   N)returnrP   r   None)__name__
__module____qualname____doc__r>   rH   rX   r\   r   r   r   r    r?   r=   r#   r#   F   s/    E
$
$6P>4"L


r?   r#   c                ^    t         | z  }t        j                  |j                  d            S )u   fixture JSON 로드 헬퍼.utf-8encoding)_FIXTURE_DIRjsonloads	read_text)r   paths     r=   _load_fixturer      s&    $D::dnngn677r?   c                    t        | d   | d   | d   | d   | j                  d      | j                  d      | d   | d   | d	   t        | j                  d
g             
      S )u1   fixture dict → SecondReviewInput 변환 헬퍼.r.   r/   r0   r1   r2   ci_gate_failure_reasonunresolved_thread_outdatedfollow_up_commit_detectedelapsed_since_follow_up_secondsowner_trigger_audit_entries
r.   r/   r0   r1   r2   r   r   r   r   r   )r   gettuple)datas    r=   _inp_from_fixturer      sy    Y{#.)01 $)B C#xx(@A#'(D#E"&'B"C(,-N(O$)$((3PRT*U$V r?   c                  8    e Zd ZdZddZddZddZddZd	dZy)
'TestPhase2StaleDetectionAndOwnerTriggeru\   Phase 2 — stale detection + owner trigger 자동 호출 테스트 (task-2565 §5 Phase 2).c                   t        d      }t        |      }t        |      }d}||u }|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                  |      dz  }t        j                  d      d	z   d
|iz  }t        t        j                  |            dx}x}}t        |      }t        j                  }	||	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                  t              rt        j                  t              ndt        j                  |	      dz  }t        j                  d|       dz   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  }t        j                  d|j                  d|d         dz   d|iz  }t        t        j                  |            dx}
x}	}y)u   fixture dev6 load → 4조건 충족 → stale 판정 + SECOND_REVIEW_TRIGGER_REQUIRED.

        fixture elapsed_since_follow_up_seconds=200 (>= 180) → grace 경과 → TRIGGER_REQUIRED.
        dev6_old_gemini_sha.jsonTr   z0%(py3)s
{%(py3)s = %(py0)s(%(py1)s)
} is %(py6)sr   inprk   rq   rr   rs   u/   dev6 fixture는 4조건 모두 충족해야 함
>assert %(py8)spy8Nrg   )zF%(py0)s == %(py4)s
{%(py4)s = %(py2)s.SECOND_REVIEW_TRIGGER_REQUIRED
}ro   r   rk   rl   rm   z-expected SECOND_REVIEW_TRIGGER_REQUIRED, got 
>assert %(py6)srs   expected_state)z-%(py2)s
{%(py2)s = %(py0)s.value
} == %(py5)s)rk   rl   r   u"   fixture expected_state 불일치: z != 
>assert %(py7)spy7)r   r   r   rv   rw   rx   ry   rz   r{   r|   r}   r~   r   r   rd   ru   )r<   r   r   r   r   @py_assert4r   @py_format9ro   @py_assert3r   r   r   @py_format8s                 r=   0test_follow_up_commit_marks_gemini_stale_on_headzXTestPhase2StaleDetectionAndOwnerTrigger.test_follow_up_commit_marks_gemini_stale_on_head  s   
 78% 6c: 	
d 	
:dB 	
 	
 	
:d 	
 	
	6	
 	
  6 	
 	
 		 6 	
 	
	6	
 	
  7: 	
 	
 		 7: 	
 	
 		 ; 	
 	
 		 ?C 	
 	
  >	
 	
 	
 	
 	

  $)HH 	
uHH 	
 	
uH 	
 	
	6	
 	
   	
 	
 		  	
 	
	6	
 	
  * 	
 	
 		 * 	
 	
 		 I 	
 	
  <E7C	
 	
 	
 	
 	
 {{ 	
d#34 	
{44 	
 	
{4 	
 	
	6	
 	
   	
 	
 		  	
 	
 		  	
 	
 		 5 	
 	
  1tDIYDZC]^	
 	
 	
 	
 	
 	
r?   c                R   t        d      }t        |      }t        ddi      }t        ||      }|d   }d}||u }|st	        j
                  d|fd	||f      t	        j                  |      t	        j                  |      d
z  }t	        j                  d|d          dz   d|iz  }	t        t	        j                  |	            dx}x}}|d   }d}||k(  }|st	        j
                  d|fd||f      t	        j                  |      t	        j                  |      d
z  }t	        j                  d|d         dz   d|iz  }	t        t	        j                  |	            dx}x}}|j                  |j                  |j                         |d   }|j                   d|j                   }||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   fixture dev6 + grace 경과 + trigger_callable mock → triggered=True, result='POSTED'.

        mock callable이 (pr_number, current_head_sha)로 호출되었는지 확인.
        r   statusr+   return_valuetrigger_callable	triggeredTr   z%(py1)s is %(py4)srq   rm   u%   triggered 기대값 True, 실제값: r   rs   Nresultrg   z%(py1)s == %(py4)s$   result 기대값 POSTED, 실제값: 
dedupe_key+assert %(py6)s)r   r   r   r   rv   rw   r{   r|   r}   r~   assert_called_once_withr/   r1   )
r<   r   r   mock_callabler   @py_assert0r   r   r   r   s
             r=   3test_second_review_owner_trigger_called_after_gracez[TestPhase2StaleDetectionAndOwnerTrigger.test_second_review_owner_trigger_called_after_grace#  s   
 78%!(/CD*3Ok"idi"d*iii"diii"iiidiii.STZ[fTgSh,iiiiiiiihh8h8+hhh8hhhhhh8hhh/STZ[cTdSg-hhhhhhhh--cmmS=Q=QRl#P#--#:N:N9O'PP#'PPPPP#'PPPP#PPP'PPPPPPPPr?   c                   t        d      }|d   |d   dddf}t        |d   |d   |d   |d   |j                  d	      |j                  d
      |d   |d   |d   |
      }t        ddi      }t	        ||      }|d   }d}||u }|st        j                  d|fd||f      t        j                  |      t        j                  |      dz  }	t        j                  d|d          dz   d|	iz  }
t        t        j                  |
            dx}x}}|d   }d}||k(  }|st        j                  d|fd||f      t        j                  |      t        j                  |      dz  }	t        j                  d|d         dz   d|	iz  }
t        t        j                  |
            dx}x}}|j                          y) u^   fixture dev6 + audit에 동일 (pr, head) POSTED 존재 → triggered=False, result='DEDUPED'.r   r/   r1   r+   test_dedupe)r/   head_shar   sourcer.   r0   r2   r   r   r   r   r   r   r   r   r   Fr   r   r   &   triggered 기대값 False, 실제값: r   rs   Nr   DEDUPEDrg   r   u%   result 기대값 DEDUPED, 실제값: )r   r   r   r   r   rv   rw   r{   r|   r}   r~   assert_not_called)r<   r   audit_entriesr   r   r   r   r   r   r   r   s              r=   .test_same_head_duplicate_owner_trigger_dedupedzVTestPhase2StaleDetectionAndOwnerTrigger.test_same_head_duplicate_owner_trigger_deduped4  s   78 "+. !34"'	+
  O;'n-!"45$(HH-F$G#'88,D#E'+,H'I&*+F&G,01R,S(5
 "(/CD*3Ok"kek"e+kkk"ekkk"kkkekkk/UV\]hViUj-kkkkkkkkhj9j9,jjj9jjjjjj9jjj0UV\]eVfUi.jjjjjjjj'')r?   c                ~   t        d      }t        |d   |d   |d   |d   |j                  d      |j                  d      |d   |d	   d
d
      }t        |      }t        j
                  }||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                  t              rt        j                  t              ndt        j                  |      dz  }t        j                  d|       dz   d|iz  }t        t        j                  |            dx}}t        |t!                     }|d   }	d}|	|u }
|
st        j                  d|
fd|	|f      t        j                  |	      t        j                  |      dz  }t        j                  d|d          dz   d|iz  }t        t        j                  |            dx}	x}
}|d   }	d}|	|k(  }
|
st        j                  d|
fd|	|f      t        j                  |	      t        j                  |      dz  }t        j                  d |d         dz   d|iz  }t        t        j                  |            dx}	x}
}y)!u   elapsed_since_follow_up_seconds=60 (< 180) → SECOND_REVIEW_PENDING + GRACE_PENDING.

        4조건은 충족하지만 grace 미경과 → pending 상태.
        r   r.   r/   r0   r1   r2   r   r   r   <   r   r   rg   )z=%(py0)s == %(py4)s
{%(py4)s = %(py2)s.SECOND_REVIEW_PENDING
}ro   r   r   u6   grace 미경과 시 SECOND_REVIEW_PENDING 기대, got r   rs   Nr   r   Fr   r   r   r   r   GRACE_PENDINGr   u+   result 기대값 GRACE_PENDING, 실제값: )r   r   r   r   r   r^   rv   rw   rx   ry   rz   r{   r|   r}   r~   r   r   )r<   r   r   ro   r   r   r   r   r   r   r   s              r=   &test_grace_not_elapsed_returns_pendingzNTestPhase2StaleDetectionAndOwnerTrigger.test_grace_not_elapsed_returns_pendingU  so   
 78O;'n-!"45$(HH-F$G#'88,D#E'+,H'I&*+F&G,.(*
  $)?? 	
u?? 	
 	
u? 	
 	
	6	
 	
   	
 	
 		  	
 	
	6	
 	
  * 	
 	
 		 * 	
 	
 		 @ 	
 	
  EUGL	
 	
 	
 	
 	

 +3Mk"kek"e+kkk"ekkk"kkkekkk/UV\]hViUj-kkkkkkkkh 	
? 	
?2 	
 	
? 	
 	
 		   	
 	
 		 $3 	
 	
  :&:J9MN	
 	
 	
 	
 	
 	
r?   c                
   t        d      }t        |      }t        |j                  |j                  |j
                  |j                  |j                  xs ddddddddd	
      }t        j                  }t        |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"                  |      dz  }
t        j$                  dt        |             dz   d|
iz  }t'        t        j(                  |            dx}x}	}|dz  }|j*                  } |       }|st        j$                  d|       dz   dt        j                         v st        j                   |      rt        j"                  |      ndt        j"                  |      t        j"                  |      dz  }t'        t        j(                  |            dx}}|dz  }|j*                  } |       }|st        j$                  d|       dz   dt        j                         v st        j                   |      rt        j"                  |      ndt        j"                  |      t        j"                  |      dz  }t'        t        j(                  |            dx}}t-        j.                  |j1                  d            }|d   }|j                  }	||	k(  }|st        j                  d |fd!||	f      t        j"                  |      d"t        j                         v st        j                   |      rt        j"                  |      nd"t        j"                  |	      d#z  }t        j$                  d$|d         d%z   d&|iz  }t'        t        j(                  |            dx}x}}	|d'   }t        j                  }	|	j2                  }||k(  }|st        j                  d |fd(||f      t        j"                  |      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  emit_phase2_markers → TRIGGER_REQUIRED marker + second-review-decision.json 생성 확인.

        Finding 1 fix: emit_phase2_markers는 이제 schema 필수 필드 검증을 수행하므로
        build_second_review_decision()의 완전한 결과를 전달해야 한다.
        r    Fr)   r*   TNONENr,   r-   
marker_dir   >=z0%(py3)s
{%(py3)s = %(py0)s(%(py1)s)
} >= %(py6)slenpathsr   u&   marker 경로 최소 2개 기대, got r   r   z/task-2565.second-review-owner-trigger-requestedu#   TRIGGER_REQUIRED marker 미생성: C
>assert %(py4)s
{%(py4)s = %(py2)s
{%(py2)s = %(py0)s.exists
}()
}trigger_markerr   z%task-2565.second-review-decision.jsonu'   second-review-decision.json 미생성: decision_jsonr   r   r.   rg   )z/%(py1)s == %(py5)s
{%(py5)s = %(py3)s.task_id
}r   )rq   rr   r   u!   decision JSON task_id 불일치: r   r   ro   )za%(py1)s == %(py7)s
{%(py7)s = %(py5)s
{%(py5)s = %(py3)s.SECOND_REVIEW_TRIGGER_REQUIRED
}.value
}r   )rq   rr   r   r   zassert %(py9)spy9)r   r   r   r.   r/   r0   r1   r2   r   rd   r   r   rv   rw   rx   ry   rz   r{   r|   r}   r~   existsr   r   r   ru   )r<   tmp_pathr   r   rS   ro   r   r   r   r   r   r   r   r   r   r   r   contentr   r   r   @py_assert6@py_format10s                          r=   %test_phase2_markers_emitted_correctlyzMTestPhase2StaleDetectionAndOwnerTrigger.test_phase2_markers_emitted_correctlyu  s    78%/KKmm)) 11$'$?$?$E2).>2#'!'"#$
 "@@#CKKXV 5zUQUzQUUUzQUUUUUUsUUUsUUUUUU5UUU5UUUzUUUQUUU"HU UUUUUUUU "$UU$$ 	
$& 	
& 	
  2.1AB	
 	
	6	
 	
   	
 	
 		  	
 	
 		 % 	
 	
 		 ' 	
 	
 	
 	
 	

 !#JJ##`#%`%``)PQ^P_'```````}```}```#```%``````**]44g4FGy! 	
S[[ 	
![0 	
 	
![ 	
 	
 		 " 	
 	
	6	
 	
  &) 	
 	
 		 &) 	
 	
 		 &1 	
 	
  0	0B/EF	
 	
 	
 	
 	
 wY#4#S#SY#S#Y#YY#YYYYY#YYYYYYYYYY#4YYY#4YYY#SYYY#YYYYYYYYr?   Nr   r   r   r   r   )	r   r   r   r   r   r   r   r   r   r   r?   r=   r   r   
  s"    f
,Q"*B
@,Zr?   r   c                  H    e Zd ZdZd
dZd
dZd
dZd
dZd
dZd
dZ	d
dZ
y	)!TestPhase3CIRerunAndPreMergeGuardu_   Phase 3 — CI rerun 자동화 + MERGE_READY pre-merge guard 테스트 (task-2565 §5 Phase 3).c                   t        d      }|d   }t        |d   |d   |d   |d   |d   |d   t        |d	         |d
         }t        |      }d}||u }|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                  |      dz  }t	        j                  d      dz   d|iz  }t        t	        j                  |            dx}x}}|d   }	d}
|	|
u }|slt	        j
                  d|fd|	|
f      t	        j                  |	      t	        j                  |
      dz  }dd|iz  }t        t	        j                  |            dx}	x}}
t        ddi      }t        ||      }|d   }	d }
|	|
k(  }|st	        j
                  d!|fd"|	|
f      t	        j                  |	      t	        j                  |
      dz  }t	        j                  d#|d         d$z   d|iz  }t        t	        j                  |            dx}	x}}
|j                  |j                   |j"                         |d%   d&   }	d'}
|	|
u }|st	        j
                  d|fd|	|
f      t	        j                  |	      t	        j                  |
      dz  }t	        j                  d(      d$z   d|iz  }t        t	        j                  |            dx}	x}}
y))u   race fixture load → should_rerun_failed_ci_jobs → True.
        auto_rerun_failed_ci_jobs with mock rerun_callable → result="POSTED".
        mock callable 호출 인자 (pr, failed_jobs) 검증.
        zrace_fresh_after_ci_fail.jsonci_rerun_inputr/   r1   r3    ci_gate_failed_with_stale_reason$current_head_unchanged_since_failureunresolved_thread_countfailed_job_identifiersprevious_rerun_attemptsr/   r1   r3   r  r  r  r  r	  Tr   r   r   r   r   u/   race fixture는 4조건 모두 충족해야 함r   r   Nexpected_should_rerunr   r   r   rs   r   zrerun triggeredr   rerun_callabler   r+   rg   r   r   r   rS   rM   Fu,   long_polling_used 항상 False 박제 위반)r   r
   r   r   rv   rw   rx   ry   rz   r{   r|   r}   r~   r   r   r   r/   r  )r<   r   ci_inp_datar   r   r   r   r   r   r   r   r   r   r   s                 r=   7test_fresh_evidence_after_ci_failure_reruns_failed_jobszYTestPhase3CIRerunAndPreMergeGuard.test_fresh_evidence_after_ci_failure_reruns_failed_jobs  s&   
 <=+,!+.();<)45S)T-89[-\1<=c1d$/0I$J#(5M)N#O$/0I$J	
 +3/ 	
4 	
/47 	
 	
 	
/4 	
 	
	6	
 	
  + 	
 	
 		 + 	
 	
	6	
 	
  ,/ 	
 	
 		 ,/ 	
 	
 		 0 	
 	
 		 48 	
 	
  >	
 	
 	
 	
 	
 +,44,4444,444,4444444444 ":K/LM*3}Mh 	
8 	
8+ 	
 	
8 	
 	
 		   	
 	
 		 $, 	
 	
  36(3C2FG	
 	
 	
 	
 	
 	--MM&&	
 j!"56 	
% 	
6%? 	
 	
6% 	
 	
 		 7 	
 	
 		 ;@ 	
 	
  ;	
 	
 	
 	
 	
 	
r?   c           	     	   t        d      }|d   }t        |d   |d   |d   |d   |d   |d   	      }t        |      }d
}||u }|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                  |      dz  }t        j                  d      dz   d|iz  }t        t        j                  |            dx}x}}|d   }	t        |	d   t        |	d         |	d   |	d         }
t        |
      \  }}|d   }||k(  }|st        j                  d|fd||f      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      dz  }t        j                  d|d   d |      d!z   d"|iz  }t        t        j                  |            dx}}|d#   }||u }|st        j                  d|fd$||f      d%t        j                         v st        j                  |      rt        j                  |      nd%t        j                  |      dz  }t        j                  d&|d#    d |       d!z   d"|iz  }t        t        j                  |            dx}}t        |
      }|d'   }d(}||u }|st        j                  d|fd)||f      t        j                  |      t        j                  |      d*z  }t        j                  d+      d,z   d-|iz  }t        t        j                  |            dx}x}}|d.   }d
}||u }|st        j                  d|fd)||f      t        j                  |      t        j                  |      d*z  }t        j                  d/      d,z   d-|iz  }t        t        j                  |            dx}x}}|d   }d0}||k(  }|slt        j                  d|fd1||f      t        j                  |      t        j                  |      d*z  }d2d-|iz  }t        t        j                  |            dx}x}}y)3u   dev5 fixture load → is_merge_ready → True.
        classify_pre_merge_commit → ("report_only", False).
        build_pre_merge_block_decision → allowed=False, redirect=True.
        !dev5_merge_ready_report_only.jsonmerge_ready_inputci_all_greenr3   r  merge_state_status%effective_diff_matches_expected_filesforbidden_path_countr  r3   r  r  r  r  Tr   r   r   mr_inpr   u/   dev5 fixture는 6조건 모두 충족해야 함r   r   Npre_merge_commit_inputrC   rD   commit_message_hintonly_touches_report_filesrC   rD   r  r  expected_change_typerg   r   rE   r   u   change_type 기대값 u   , 실제값: r   r   !expected_pre_merge_commit_allowedr   allowedu   allowed 기대값 rF   Fr   r   uC   merge_ready + report_only → pre_merge_commit_allowed=False 기대r   rs   rG   uI   merge_ready + report_only → redirect_to_post_merge_evidence=True 기대rB   r   r   )r   r   r   rv   rw   rx   ry   rz   r{   r|   r}   r~   r   r   r   r   )r<   r   mr_datar  r   r   r   r   r   pm_datapm_inprE   r  r   r   r   rS   r   r   r   s                       r=   +test_merge_ready_report_only_commit_blockedzMTestPhase3CIRerunAndPreMergeGuard.test_merge_ready_report_only_commit_blocked  s   
 @A *+  0)01O)P$+,E$F&';<29:a2b!()?!@
 f% 	
 	
%- 	
 	
 	
% 	
 	
	6	
 	
   	
 	
 		  	
 	
	6	
 	
  % 	
 	
 		 % 	
 	
 		 & 	
 	
 		 *. 	
 	
  >	
 	
 	
 	
 	

 /0$. 89 '(= >&-.I&J	
  9@W"#9: 	
{:: 	
 	
{: 	
 	
	6	
 	
   	
 	
 		  	
 	
 		 ; 	
 	
  %T*@%A$DMR]Q`a	
 	
 	
 	
 	
 BC 	
wCC 	
 	
wC 	
 	
	6	
 	
   	
 	
 		  	
 	
 		 D 	
 	
  !&I!J K=Y`Xab	
 	
 	
 	
 	

 2&923 	
u 	
3u< 	
 	
3u 	
 	
 		 4 	
 	
 		 8= 	
 	
  R	
 	
 	
 	
 	
 9: 	
d 	
:dB 	
 	
:d 	
 	
 		 ; 	
 	
 		 ?C 	
 	
  X	
 	
 	
 	
 	
 &7-7&-7777&-777&777-7777777r?   c                n   t        d      }|d   }t        |d   t        |d         |d   |d         }t        |      }t	        |      }d}||v }|st        j                  d	|fd
||f      t        j                  |      dt        j                         v st        j                  |      rt        j                  |      nddz  }t        j                  d      dz   d|iz  }	t        t        j                  |	            dx}}|d   }d}
||
k(  }|st        j                  d|fd||
f      t        j                  |      t        j                  |
      dz  }t        j                  d|d         dz   d|iz  }t        t        j                  |            dx}x}}
|d   }d}
||
v }|st        j                  d	|fd||
f      t        j                  |      t        j                  |
      dz  }t        j                  d|d         dz   d|iz  }t        t        j                  |            dx}x}}
|d   }t        |t              }|sddt        j                         v st        j                  t              rt        j                  t              nd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}}y)!u   blocked decision → post_merge_evidence_redirect_payload.
        redirect_to 필드 존재 + original_change_type=report_only.
        r  r  rC   rD   r  r  r  redirect_toin)z%(py1)s in %(py3)sredirect_payload)rq   rr   u-   redirect_payload에 redirect_to 필드 필수r   r   Noriginal_change_typerB   rg   r   r   u9   original_change_type 기대값 'report_only', 실제값: r   rs   )zpost-merge smokezreconcile evidencezlifecycle marker)z%(py1)s in %(py4)su   redirect_to 허용 값 위반: original_changed_filesz5assert %(py5)s
{%(py5)s = %(py0)s(%(py2)s, %(py3)s)
}rn   list)rk   rl   rr   r   )r   r   r   r   r   rv   rw   r{   rx   ry   rz   r|   r}   r~   rn   r+  )r<   r   r!  r"  blocked_decisionr(  r   r   r   r   r   r   r   r   r   s                  r=   )test_post_merge_evidence_redirect_allowedzKTestPhase3CIRerunAndPreMergeGuard.test_post_merge_evidence_redirect_allowed  s    @A/0$. 89 '(= >&-.I&J	
 :&A?@PQ 	
} 00 	
 	
} 0 	
 	
 		  	
 	
	6	
 	
  !1 	
 	
 		 !1 	
 	
  <	
 	
 	
 	
 	
   67 	
= 	
7=H 	
 	
7= 	
 	
 		 8 	
 	
 		 <I 	
 	
  HHXYoHpGst	
 	
 	
 	
 	
  . 	
 3
 	
. 3
 
 	
 		
. 3
 	
 	
 
		 / 	
 	
 
		3
 	
 	
  ..>}.M-PQ	
 	
 	
 		
 	

 ++CDKzDdKKKKKKKKzKKKzKKKDKKKKKKdKKKdKKKKKKKKKKr?   c                N   t        dddddd      }t        |      }d}||u }|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                  |      d
z  }t        j                  d      dz   d|iz  }t        t        j                  |            dx}x}}t        dddddd      }t        |      }d}||u }|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                  |      d
z  }t        j                  d      dz   d|iz  }t        t        j                  |            dx}x}}t        dddddd      }t        |      }d}||u }|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                  |      d
z  }t        j                  d      dz   d|iz  }t        t        j                  |            dx}x}}y)u   MergeReadyInput with ci_all_green=False → is_merge_ready → False.
        또는 gemini_fresh_on_current_head=False → False.
        stale CI 또는 stale Gemini 상태에서는 merge 불허.
        FTr   CLEANr  r   r   r   inp_stale_cir   u2   ci_all_green=False → is_merge_ready=False 기대r   r   Ninp_stale_geminiuB   gemini_fresh_on_current_head=False → is_merge_ready=False 기대BEHIND
inp_behindu9   merge_state_status=BEHIND → is_merge_ready=False 기대)r   r   rv   rw   rx   ry   rz   r{   r|   r}   r~   )	r<   r0  r   r   r   r   r   r1  r3  s	            r=   test_stale_ci_merge_forbiddenz?TestPhase3CIRerunAndPreMergeGuard.test_stale_ci_merge_forbidden#  sT    ')-$%&26!"
 l+ 	
u 	
+u4 	
 	
 	
+u 	
 	
	6	
 	
   	
 	
 		  	
 	
	6	
 	
  + 	
 	
 		 + 	
 	
 		 , 	
 	
 		 05 	
 	
  A	
 	
 	
 	
 	

 +).$%&26!"
 ./ 	
5 	
/58 	
 	
 	
/5 	
 	
	6	
 	
   	
 	
 		  	
 	
	6	
 	
  / 	
 	
 		 / 	
 	
 		 0 	
 	
 		 49 	
 	
  Q	
 	
 	
 	
 	

 %)-$%'26!"

 j) 	
U 	
)U2 	
 	
 	
)U 	
 	
	6	
 	
   	
 	
 		  	
 	
	6	
 	
  ) 	
 	
 		 ) 	
 	
 		 * 	
 	
 		 .3 	
 	
  H	
 	
 	
 	
 	
 	
r?   c           
        d}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                  |      dz  }t        j                  dt                dz   d|iz  }t        t        j                  |            d	x}}t        d
ddddddd      }t        |      }d}||u }|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                  |      dz  }t        j                  d      dz   d|iz  }	t        t        j                  |	            d	x}x}}t        ddi      }
t        ||
      }|d   }d}||k(  }|st        j                  d|fd||f      t        j                  |      t        j                  |      d z  }t        j                  d!|d         d"z   d#|iz  }t        t        j                  |            d	x}x}}|
j                          |d$   d%   }d}||u }|slt        j                  d|fd&||f      t        j                  |      t        j                  |      d z  }d'd#|iz  }t        t        j                  |            d	x}x}}y	)(u   CIRerunInput with previous_rerun_attempts=3 (cap).
        should_rerun_failed_ci_jobs → False.
        auto_rerun_failed_ci_jobs → result="CAP_EXCEEDED".
           rg   r   r	   r   z%MAX_CI_RERUN_PER_HEAD must be 3, got r   r   Ni abc12345Tr   zgemini-review-gater
  Fr   r   r   r   r   u=   previous_rerun_attempts=3 (cap) → should_rerun=False 기대r   r   r   r   r   r  r   CAP_EXCEEDEDr   r   u*   result 기대값 CAP_EXCEEDED, 실제값: r   rs   rS   rM   r   r   )r	   rv   rw   rx   ry   rz   r{   r|   r}   r~   r
   r   r   r   r   )r<   r   r   r   r   r   r   r   r   r   r   r   r   r   r   s                  r=   (test_ci_rerun_cap_prevents_infinite_loopzJTestPhase3CIRerunAndPreMergeGuard.test_ci_rerun_cap_prevents_infinite_loopO  s   
 )* 	
$) 	
 	
$ 	
 	
	6	
 	
  % 	
 	
 		 % 	
 	
 		 )* 	
 	
  44I3JK	
 	
 	
 	
 	
 ')--115$%#:$%	
 +3/ 	
5 	
/58 	
 	
 	
/5 	
 	
	6	
 	
  + 	
 	
 		 + 	
 	
	6	
 	
  ,/ 	
 	
 		 ,/ 	
 	
 		 0 	
 	
 		 49 	
 	
  L	
 	
 	
 	
 	

 "+/FG*3}Mh 	
> 	
>1 	
 	
> 	
 	
 		   	
 	
 		 $2 	
 	
  99I8LM	
 	
 	
 	
 	
 	'')j!"56?%?6%????6%???6???%???????r?   c           
        t        dddddddd      }t        |      }d}||u }|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                  |      dz  }t        j                  d      dz   d|iz  }t        t        j                  |            dx}x}}t        ddi      }t        ||      }|d   }	d}
|	|
k(  }|st        j                  d|fd|	|
f      t        j                  |	      t        j                  |
      dz  }t        j                  d|d         dz   d|iz  }t        t        j                  |            dx}	x}}
|j                          y)u   gemini_fresh_on_current_head=False.
        should_rerun_failed_ci_jobs → False.
        auto_rerun_failed_ci_jobs → result="SKIPPED".
        i def67890FTr   r8  r
  r   r   r   r   r   u0   gemini_fresh=False → should_rerun=False 기대r   r   Nr   r   r   r  r   SKIPPEDrg   r   r   u%   result 기대값 SKIPPED, 실제값: r   rs   )r
   r   rv   rw   rx   ry   rz   r{   r|   r}   r~   r   r   r   )r<   r   r   r   r   r   r   r   r   r   r   r   s               r=   ,test_ci_rerun_skipped_without_fresh_evidencezNTestPhase3CIRerunAndPreMergeGuard.test_ci_rerun_skipped_without_fresh_evidences  s   
 ').-115$%#:$%	
 +3/ 	
5 	
/58 	
 	
 	
/5 	
 	
	6	
 	
  + 	
 	
 		 + 	
 	
	6	
 	
  ,/ 	
 	
 		 ,/ 	
 	
 		 0 	
 	
 		 49 	
 	
  ?	
 	
 	
 	
 	

 "+/FG*3}Mh 	
9 	
9, 	
 	
9 	
 	
 		   	
 	
 		 $- 	
 	
  4F84D3GH	
 	
 	
 	
 	
 	'')r?   c                6   t        dddd      }t        |      \  }}d}||u }|st        j                  d|fd||f      dt	        j
                         v st        j                  |      rt        j                  |      ndt        j                  |      d	z  }t        j                  d
|       dz   d|iz  }t        t        j                  |            dx}}d}||v }|st        j                  d|fd||f      dt	        j
                         v st        j                  |      rt        j                  |      ndt        j                  |      d	z  }t        j                  d|      dz   d|iz  }t        t        j                  |            dx}}y)u   commit message hint = "fix:", changed_files includes anu_v2/*.py.
        classify_pre_merge_commit → ("code_fix" or "test_fix", True) — 허용.
        merge_ready=True이더라도 코드 수정 commit은 허용되어야 함.
        T)rA   zanu_v2/merge_queue_executor.pyu"   fix: CI rerun 조건 버그 수정Fr  r   r   r  r   uS   코드 변경 commit은 허용되어야 함 — allowed 기대값 True, 실제값: r   r   N)code_fixtest_fixevidence_requiredr&  )z%(py0)s in %(py3)srE   uF   change_type 기대값 code_fix/test_fix/evidence_required, 실제값: r   r   rv   rw   rx   ry   rz   r{   r|   r}   r~   r<   r   rE   r  r   r   r   r   s           r=   (test_merge_ready_code_fix_commit_allowedzJTestPhase3CIRerunAndPreMergeGuard.test_merge_ready_code_fix_commit_allowed  s   
 " !E&+
  9=W 	
w$ 	
 	
w$ 	
 	
	6	
 	
   	
 	
 		  	
 	
 		  	
 	
  bbiajk	
 	
 	
 	
 	
 L 	
{KK 	
 	
{K 	
 	
	6	
 	
   	
 	
 		  	
 	
 		 L 	
 	
  UU`Tcd	
 	
 	
 	
 	
r?   Nr   )r   r   r   r   r  r#  r-  r4  r:  r>  rE  r   r?   r=   r  r    s.    i'
R18fL:*
X"@H*>
r?   r  c                  4    e Zd ZdZddZ	 	 	 	 	 	 ddZddZy)TestGeminiMediumFixesu4   PR #119 Gemini medium 3건 Fix 검증 (task-2568+1).c                   t        d      }dddddf}t        |d   d|d   d|j                  d	      |j                  d
      |d   |d   |d   |
      }t        |      }d}||u }|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                  |      dz  }t	        j                  d      dz   d|iz  }t        t	        j                  |            dx}x}}y)u  entry의 pr_number=None 일 때 'pr' fallback이 사용되어 dedupe 매칭 성공.

        Fix 1: entry_key = f"{entry.get('pr_number') or entry.get('pr') or ''}+..."
        pr_number=None → falsy → pr=119 fallback → key "119+abc" 생성 → 매칭 True.
        r   Nw   abcr+   )r/   prr   r   r.   r0   r2   r   r   r   r   r   Tr   r   r   r   r   uY   pr_number=None 이면 'pr' fallback 사용해 키 '119+abc' 생성 → dedupe=True 기대r   r   )r   r   r   r   rv   rw   rx   ry   rz   r{   r|   r}   r~   )	r<   r   r   r   r   r   r   r   r   s	            r=   ,test_dedupe_entry_key_handles_none_pr_numberzBTestGeminiMediumFixes.test_dedupe_entry_key_handles_none_pr_number  s    78 "!"	+
  On-"$(HH-F$G#'88,D#E'+,H'I&*+F&G,01R,S(5
 (, 	
 	
,4 	
 	
 	
, 	
 	
	6	
 	
  ( 	
 	
 		 ( 	
 	
	6	
 	
  ), 	
 	
 		 ), 	
 	
 		 - 	
 	
 		 15 	
 	
  h	
 	
 	
 	
 	
 	
r?   c                P   ddl }dd}|j                  |d|       d}t        j                  t        d      5  t        |dd	i|
       ddd       t        |j                  d| d            }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                  |      dz  }
t        j                  d|       dz   d|
iz  }t!        t        j"                  |            dx}x}	}y# 1 sw Y   SxY w)u   os.replace 실패 시 임시 파일(.tmp) 잔류 0건 보장.

        Fix 2: try/finally + tmp_path.unlink(missing_ok=True) 로 cleanup 보장.
        r   Nc                     ~ ~t        d      )Ninjected os.replace failure)OSError)argskwargss     r=   _raise_on_replacez`TestGeminiMediumFixes.test_write_marker_cleanup_on_os_replace_failure.<locals>._raise_on_replace  s    f788r?   replaceztest-cleanup.jsonrO  rK   xr   )payloadr   .z.*.tmprg   z0%(py3)s
{%(py3)s = %(py0)s(%(py1)s)
} == %(py6)sr   	tmp_filesr   uM   OSError 발생 후 .tmp 파일이 잔류해서는 안 됨 — 잔류 파일: r   r   )rQ  objectrR  rZ  r   r   )ossetattrrQ   rR   rP  r   r+  globr   rv   rw   rx   ry   rz   r{   r|   r}   r~   )r<   r   monkeypatch_osrS  marker_namerY  r   r   r   r   r   s               r=   /test_write_marker_cleanup_on_os_replace_failurezETestGeminiMediumFixes.test_write_marker_cleanup_on_os_replace_failure  s    		9 	C,=>)]]7*GH 	MsAh8L	M ;-v'>?@	9~ 	
 	
~" 	
 	
 	
~ 	
 	
	6	
 	
   	
 	
 		  	
 	
	6	
 	
   	
 	
 		  	
 	
 		  	
 	
 		 "# 	
 	
  \\e[fg	
 	
 	
 	
 	
 	
	M 	Ms   FF%c                2   t        dddd      }t        |      \  }}d}||k7  }|st        j                  d|fd||f      d	t	        j
                         v st        j                  |      rt        j                  |      nd	t        j                  |      d
z  }t        j                  d|      dz   d|iz  }t        t        j                  |            dx}}d}||u }|st        j                  d|fd||f      dt	        j
                         v st        j                  |      rt        j                  |      ndt        j                  |      d
z  }t        j                  d      dz   d|iz  }t        t        j                  |            dx}}y)u   anu_v2/lifecycle/utils.py 는 memory/ 외부 경로 → lifecycle_files 미매칭 → report_only 아님.

        Fix 3: (f.startswith("memory/") and "/lifecycle/" in f) 조건으로 비-memory 경로 제외.
        T)zanu_v2/lifecycle/utils.pyzfix: anu_v2 lifecycle utilFr  rB   )!=)z%(py0)s != %(py3)srE   r   ue   anu_v2/lifecycle/utils.py 는 memory/ 외부 경로 → report_only 가 아니어야 함, 실제값: r   r   Nr   r   r  uB   code_fix 분류면 allowed=True 기대 (merge_ready commit 허용)rC  rD  s           r=   Atest_detect_change_type_does_not_match_non_memory_lifecycle_pathszWTestGeminiMediumFixes.test_detect_change_type_does_not_match_non_memory_lifecycle_paths  ss   
 "8 <&+	
  9=W+ 	
{m+ 	
 	
{m 	
 	
 
6	
 	
   	
 	
 
	  	
 	
 
	 , 	
 	
 %*	
 	
 	
 	
 	
  	
w$ 	
 	
w$ 	
 	
	6	
 	
   	
 	
 		  	
 	
 		  	
 	
  Q	
 	
 	
 	
 	
r?   Nr   )r   r   r^  zpytest.MonkeyPatchr   r   )r   r   r   r   rL  ra  rd  r   r?   r=   rG  rG    s-    >
B

+=
	
4
r?   rG  c                  P    e Zd ZdZ	 	 	 	 ddZ	 	 	 	 ddZd	dZd	dZ	 	 	 	 ddZy)
!TestGeminiFreshReview3Regressionsu5  PR #119 attempt-3 Gemini fresh review 3건 회귀 테스트 (task-2568+1).

    Finding 1 (HIGH): decision_payload schema 누락 시 ValueError
    Finding 2 (MEDIUM): lifecycle 경로 다양성 — 기능 회귀 없음
    Finding 3 (MEDIUM): fcntl ImportError → NotImplementedError (Linux-only guard)
    c                    dddd}t         j                  }t        j                  t        d      5  t        d|||       d	d	d	       y	# 1 sw Y   y	xY w)
u   decision에 필수 schema 필드가 누락되면 ValueError raise.

        Finding 1 fix: emit_phase2_markers 내부에서 _SCHEMA_V1_REQUIRED 대비
        누락 필드를 검사하고 ValueError를 raise한다.
        Tr+   z107+cee55afe)r   r   r   rN   rK   r%   r   N)r   rd   rQ   rR   
ValueErrorr   )r<   r   partial_decisionro   s       r=   Ctest_emit_phase2_markers_raises_valueerror_on_missing_schema_fieldszeTestGeminiFreshReview3Regressions.test_emit_phase2_markers_raises_valueerror_on_missing_schema_fields  sY     ("

 "@@]]:_= 	[U4DQYZ	[ 	[ 	[s   AAc                   t        dddddddddd	d
dd      }t        j                  }t        d|||      }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                  |      dz  }t        j                  d      dz   d|iz  }	t        t        j                  |	            d
x}x}}y
)u   build_second_review_decision()의 완전한 결과를 넘기면 ValueError 없이 정상 동작.

        Finding 1 fix의 happy-path 확인: 완전한 schema dict이면 검증 통과.
        r%   rI  r'   r(   Fr)   r*   Tr+   Nr,   r-   r   r   r   r   r   r   r   u7   marker 경로가 최소 1개 이상 반환되어야 함r   r   )r   r   rd   r   r   rv   rw   rx   ry   rz   r{   r|   r}   r~   )
r<   r   complete_decisionro   r   r   r   r   r   r   s
             r=   6test_emit_phase2_markers_passes_with_complete_decisionzXTestGeminiFreshReview3Regressions.test_emit_phase2_markers_passes_with_complete_decision#  s     9#'$.).>2#'!)"#$
 "@@ $K8IV^_5zYQYzQYYYzQYYYYYYsYYYsYYYYYY5YYY5YYYzYYYQYYY YYYYYYYYr?   c                2   t        dddd      }t        |      \  }}d}||k(  }|st        j                  d|fd||f      d	t	        j
                         v st        j                  |      rt        j                  |      nd	t        j                  |      d
z  }t        j                  d|      dz   d|iz  }t        t        j                  |            dx}}d}||u }|st        j                  d|fd||f      dt	        j
                         v st        j                  |      rt        j                  |      ndt        j                  |      d
z  }t        j                  d      dz   d|iz  }t        t        j                  |            dx}}y)u  memory/somedir/lifecycle/event.json → lifecycle_files 매칭 → report_only.

        Finding 2 fix 후에도 memory/ 하위 /lifecycle/ 포함 경로는 정상 매칭됨을 확인.
        중복 조건 제거가 기능 회귀를 유발하지 않음을 검증.
        T)z#memory/somedir/lifecycle/event.jsonzlifecycle: add eventFr  rB   rg   r   rE   r   u\   memory/somedir/lifecycle/event.json 는 lifecycle 경로 → report_only 기대, 실제값: r   r   Nr   r   _allowedK   report_only 분류면 allowed=False 기대 (lifecycle 경로 커밋 차단)rC  r<   r   rE   ro  r   r   r   r   s           r=   Atest_classify_pre_merge_commit_deep_lifecycle_path_is_report_onlyzcTestGeminiFreshReview3Regressions.test_classify_pre_merge_commit_deep_lifecycle_path_is_report_onlyA  st    "B 6&+	
 !:# >X+ 	
{m+ 	
 	
{m 	
 	
 
6	
 	
   	
 	
 
	  	
 	
 
	 , 	
 	
 %*	
 	
 	
 	
 	
 ! 	
x5  	
 	
x5 	
 	
	6	
 	
   	
 	
 		  	
 	
 		 ! 	
 	
  Z	
 	
 	
 	
 	
r?   c                2   t        dddd      }t        |      \  }}d}||k(  }|st        j                  d|fd||f      d	t	        j
                         v st        j                  |      rt        j                  |      nd	t        j                  |      d
z  }t        j                  d|      dz   d|iz  }t        t        j                  |            dx}}d}||u }|st        j                  d|fd||f      dt	        j
                         v st        j                  |      rt        j                  |      ndt        j                  |      d
z  }t        j                  d      dz   d|iz  }t        t        j                  |            dx}}y)u   memory/lifecycle/event.json → lifecycle_files 매칭 → report_only.

        Finding 2: f.startswith("memory/lifecycle/") 조건 제거 후에도
        (f.startswith("memory/") and "/lifecycle/" in f) 조건으로 올바르게 매칭됨.
        T)zmemory/lifecycle/event.jsonzlifecycle: direct pathFr  rB   rg   r   rE   r   uT   memory/lifecycle/event.json 는 lifecycle 경로 → report_only 기대, 실제값: r   r   Nr   r   ro  rp  rC  rq  s           r=   Ctest_classify_pre_merge_commit_direct_lifecycle_path_is_report_onlyzeTestGeminiFreshReview3Regressions.test_classify_pre_merge_commit_direct_lifecycle_path_is_report_onlyX  st    ": 8&+	
 !:# >X+ 	
{m+ 	
 	
{m 	
 	
 
6	
 	
   	
 	
 
	  	
 	
 
	 , 	
 	
 %*	
 	
 	
 	
 	
 ! 	
x5  	
 	
x5 	
 	
	6	
 	
   	
 	
 		  	
 	
 		 ! 	
 	
  Z	
 	
 	
 	
 	
r?   c                   ddl m} |dz  }dddddd	} |||
      }|j                  } |       }|st        j                  d|       dz   dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      dz  }t        t        j                  |            dx}}|j                  d      j                         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                  |      dz  }t        j                  dt        |	       d      dz   d|iz  }t        t        j                  |            dx}
x}}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}
}|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)"u-  Linux 환경에서 append_owner_trigger_audit 정상 동작 확인.

        Finding 3 fix: try/except ImportError → NotImplementedError 추가.
        Linux self-hosted ANU CI에서는 fcntl import가 성공하므로
        NotImplementedError 없이 audit 항목이 기록되어야 함.
        r   )append_owner_trigger_auditzowner_trigger_audit.jsonlz2026-05-13T00:00:00ZrI  r(   r+   test_finding3)	timestampr/   r   r   r   )
audit_pathu%   audit 파일이 생성되어야 함: r   result_pathr   Nr   r   r   rg   rX  r   linesr   u"   audit 항목 1건 기대, 실제: u   건r   r   r/   r   r   r   rs   r   )anu_v2.second_review_recoveryrv  r   rv   r|   rx   ry   rz   r{   r}   r~   r   strip
splitlinesr   rw   r   r   )r<   r   rv  
audit_fileentryrz  r   r   r   r{  r   r   r   r   r   recordedr   s                    r=   5test_append_owner_trigger_audit_linux_import_succeedszWTestGeminiFreshReview3Regressions.test_append_owner_trigger_audit_linux_import_succeedsq  s(    	M ;;
/"%
 1:N!!Z!#Z#ZZ'L[M%ZZZZZZZ{ZZZ{ZZZ!ZZZ#ZZZZZZ%%w%7==?JJL5zTQTzQTTTzQTTTTTTsTTTsTTTTTT5TTT5TTTzTTTQTTT"DSZLPS TTTTTTTT::eAh'$++$++++$+++$++++++++++!-X-!X----!X---!---X-------r?   Nr   r   )	r   r   r   r   rj  rm  rr  rt  r  r   r?   r=   rf  rf    sV    [[	[&ZZ	Z<
.
2..	.r?   rf  )r   rp   r   rP   )r   rP   r   r   )5r   
__future__r   builtinsrx   _pytest.assertion.rewrite	assertionrewriterv   r   pathlibr   unittest.mockr   rQ   anu_v2.polling_policyr   r   r   r|  r	   r
   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   __file__parentr   r#   r   r   r   r  rG  rf  r   r?   r=   <module>r     s   * #     #  
      8 	N  :-0QQ h
 h
\8 WZ WZzA
 A
NR
 R
pF. F.r?   