
     jw                       d Z ddlmZ ddlZddlZddlZddlmZ ddlZ ee	      j                         j                  j                  j                  Z ee      ej                  v r!ej                  j                   ee             ej                  j!                  d ee             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' d d!dZ(d"d#dZ)dd	gZ*d
dddZ+ddgg dZ,dg ddZ-ej\                  d$d       Z/	 	 	 	 	 d%	 	 	 	 	 d&dZ0d'dZ1d'dZ2d'dZ3d'dZ4d'dZ5d'dZ6d(dZ7d(dZ8d'dZ9d(dZ:d(dZ;d)dZ<y)*uI  task-2509+1 회귀 테스트 — review_gate_passed + fallback_review_passed 12 케이스.

QA 담당: 모리건(Morrigan)
대상: utils/merge_queue_executor.py (task-2509+1 보강)

케이스 목록 (회장 §1~12):
  TC-01  Gemini COMPLETED + unresolved 0 → review_gate_passed=true
  TC-02  Gemini UNAVAILABLE_QUOTA + fallback PASS → review_gate_passed=true (★ PR #58 fixture 재현)
  TC-03  Gemini UNAVAILABLE_QUOTA + fallback FAIL → BLOCK (FALLBACK_REVIEW_FAILED)
  TC-04  Gemini TIMEOUT + fallback PASS → AUTO_MERGE_ALLOWED
  TC-05  Gemini UNRESOLVED + real_bug → BLOCK (GEMINI_UNRESOLVED_BLOCK)
  TC-06  Gemini SCOPE_EXPANSION → CRITICAL_GEMINI_SCOPE_EXPANSION
  TC-07  smoke_command=None + dry_run=True → 정상 진행 (WARN/SKIP 허용)
  TC-08  smoke_command=None + dry_run=False → BLOCK (NON_DRY_RUN_REQUIRES_SMOKE_COMMAND)
  TC-09  HIGH_CORE risk file 변경 + Gemini quota → static_risky_pattern_scan 호출
  TC-10  후행 PR stale 재검증에서 BEHIND 감지
  TC-11  후행 PR effective diff 오염 감지
  TC-12  audit JSON에 신규 7 필드 기록 검증
    )annotationsN)Path)AUTO_MERGE_ALLOWEDBLOCKED_WITH_REASONCRITICAL_GEMINI_SCOPE_EXPANSIONFALLBACK_REVIEW_FAILEDGEMINI_COMPLETEDGEMINI_REAL_BUGGEMINI_SCOPE_EXPANSIONGEMINI_TIMEOUTGEMINI_UNAVAILABLE_QUOTAGEMINI_UNRESOLVEDGEMINI_UNRESOLVED_BLOCK"NON_DRY_RUN_REQUIRES_SMOKE_COMMANDRISK_LEVEL_HIGH_CORERISK_LEVEL_LOWExecutorContextQueueDecisionTaskSpecassess_risk_levelevaluate_prrecheck_following_prsstatic_risky_pattern_scanverify_head_lock_then_mergec                4    t        j                  g | ||      S )u   CompletedProcess 생성 헬퍼.)args
returncodestdoutstderr)
subprocessCompletedProcess)r   r   r   s      T/home/jay/workspace/tests/regression/test_merge_queue_executor_review_gate_2509p1.pycpr#   ?   s    &&B:f]cdd    c                *     g d fd	}|_         |S )u   args 패턴별 CompletedProcess 반환 fake runner.

    key = tuple of arg tokens to match (subset of args),
    value = CompletedProcess to return.
    Unmatched → returncode=0, empty stdout/stderr.
    c                     j                  t               ||d       xs i j                         D ]  \  }}t         fd|D              s|c S  t	               S )N)r   cwdtimeoutc              3  &   K   | ]  }|v  
 y wN ).0pr   s     r"   	<genexpr>z.make_runner.<locals>.runner.<locals>.<genexpr>P   s     .19.s   )appendlistitemsallr#   )r   r'   r(   pattern	completedcallsreturns_by_argss   `    r"   runnerzmake_runner.<locals>.runnerM   sY    d4jIJ#2#8b"?"?"A 	!GY.g..  	! tr$   )N<   )r5   )r6   r7   r5   s   ` @r"   make_runnerr9   D   s     E FLMr$   utils/merge_queue_executor.py@tests/regression/test_merge_queue_executor_review_gate_2509p1.pyCLEANzsha-clean-001main)mergeStateStatus
headRefOidbaseRefNameSUCCESS)statusdetailsrawok)rB   
unresolvedhookc                 D    t        dt        t              dg ddddd 	      S )Ntask-2509+1z'merge_queue/review_gate/fallback_reviewserial_only   TF)	task_idexpected_files	risk_area
dependencyparallel_policymerge_queue_positionstale_recheck_requiredcherry_pick_allowedsmoke_command)r   r0   EXPECTED_FILESr+   r$   r"   serial_task_specrV   i   s/    N+;%#!
 
r$   c                >    | t        i       } t        | ||d||      S )u%   기본 ExecutorContext 생성 헬퍼.T)r7   
pr_workdirrT   no_auditfixture_main_shamain_log_grep)r9   r   )r7   rX   rT   rZ   r[   s        r"   	_make_ctxr\   x   s1     ~R#)# r$   c           
        t               }dg}t        |      | _        t        d| dt        |      t        t
        t        |      }|j                  t        k(  sJ d|j                          |j                  du sJ d|j                          |j                  du sJ d	|j                          |j                  t        k(  s!J d
|j                   d|j                          y)u   Gemini status=ok + unresolved 0 → gemini_status=GEMINI_COMPLETED,
    review_gate_passed=true, fallback_review_used=false, AUTO_MERGE_ALLOWED.r;   :   zsha-pr-tc01	pr_number	task_specpr_head_shaeffective_filesmerge_stateci_stategemini_statectxzexpected GEMINI_COMPLETED, got Tz<review_gate_passed must be True for COMPLETED+unresolved=0: Fz1fallback must NOT be used when Gemini COMPLETED: !expected AUTO_MERGE_ALLOWED, got 
 / reason=N)r\   r0   rM   r   CLEAN_MERGE_STATECI_OK	GEMINI_OKgemini_statusr	   review_gate_passedreasonfallback_review_usedfallback_check_detailsdecisionr   )rV   rg   low_risk_filesrr   s       r"   -test_tc01_gemini_completed_review_gate_passesrt      s    +C 	KN '+>&:#"!^,%	H !!%55 
)(*@*@)AB5 &&$. 
FxFWX. ((E1 
;H<[<[;\]1  22 
+H,=,=+>jHYZ2r$   c           
        g d}t        |      }dg dddigd}dg}t        |      | _        t        d	| d
t        |      t        t
        ||      }|j                  t        k(  sJ d|j                          |j                  du sJ d       |j                  du sJ d|j                          |j                  du sJ d|j                          |j                  t        k(  s!J d|j                   d|j                          y)u   ★ PR #58 fixture 재현 — Gemini quota daily limit 상황에서
    fallback_review 8조건 모두 PASS이면 review_gate_passed=true이고
    AUTO_MERGE_ALLOWED 결정을 받아야 한다.python3z-mpytestztests/smokerT   unavailable_quotaNmessageQuota exceeded for daily limitrB   rF   rG   errorsr;   r^   38334b09r_   'expected GEMINI_UNAVAILABLE_QUOTA, got Tz>fallback_review_used must be True for Gemini UNAVAILABLE_QUOTAzfallback failed: z!review_gate_passed must be True: rh   ri   )r\   r0   rM   r   rj   rk   rm   r   rp   fallback_review_passedrq   rn   ro   rr   r   )rV   	smoke_cmdrg   gemini_quotars   rr   s         r"   0test_tc02_gemini_unavailable_quota_fallback_passr      sX   
 ;I
)
,C &?@A	L 	KN '+>&:#"^,%!	H !!%== 
1(2H2H1IJ= ((D0 H0 **d2 
H;;<=2 &&$. 
+HOO+<=.  22 
+H,=,=+>jHYZ2r$   c           
     
   t        d      }dg dddigd}dg}t        |      | _        t        d| d	t        |      t        t
        ||
      }|j                  t        k(  sJ d|j                          |j                  du sJ |j                  du sJ d|j                          |j                  du sJ |j                  t        k(  sJ d|j                          t        |j                  v sJ d|j                          y)u   Gemini quota 미가용이지만 fallback 8조건 중 1건 이상 FAIL 시
    BLOCKED_WITH_REASON + reason에 FALLBACK_REVIEW_FAILED.Nry   rz   r{   r|   r}   r;   r^   zsha-pr-tc03r_   r   TFz,fallback must FAIL when smoke_command=None: "expected BLOCKED_WITH_REASON, got z,reason must mention FALLBACK_REVIEW_FAILED: )r\   r0   rM   r   rj   rk   rm   r   rp   r   rq   rn   rr   r   r   ro   )rV   rg   r   rs   rr   s        r"   7test_tc03_gemini_unavailable_quota_fallback_fail_blocksr      sN    $
'C &?@A	L 	KN '+>&:#"!^,%!	H !!%== 
1(2H2H1IJ= ((D000**e3 
6x7V7V6WX3 &&%/// 33 
,X->->,?@3 "X__4 
6x6GH4r$   c           
        g d}t        |      }dg dddigd}dg}t        |      | _        t        d	| d
t        |      t        t
        ||      }|j                  t        k(  sJ d|j                          |j                  du sJ |j                  du sJ d|j                          |j                  du sJ |j                  t        k(  s!J d|j                   d|j                          y)uH   Gemini polling timeout이지만 fallback 8조건 모두 PASS → 통과.rv   ry   r(   Nr{   zPolling deadline exceededr}   r;   r^   zsha-pr-tc04r_   zexpected GEMINI_TIMEOUT, got Tzfallback must PASS: rh   ri   )r\   r0   rM   r   rj   rk   rm   r   rp   r   rq   rn   rr   r   ro   )rV   r   rg   gemini_timeoutrs   rr   s         r"   &test_tc04_gemini_timeout_fallback_passr   !  s1   :I
)
,C :;<	N 	KN '+>&:#"!^,%#	H !!^3 
'(>(>'?@3 ((D000**d2 
x>>?@2 &&$... 22 
+H,=,=+>jHYZ2r$   c           
     h   t               }ddddgdddgddd}t        d| d	t        t              t        t
        ||
      }|j                  t        t        fv sJ d|j                          |j                  t        k(  s!J d|j                   d|j                          |j                  du sJ y)u   Gemini auto_triage_candidate (inside expected) → GEMINI_UNRESOLVED_BLOCK.
    real_bug=True 분류 시에도 머지 차단되어야 한다.auto_triage_candidater:   zreal bug: NPE on line 42pathbodyauto_gemini_triageT)rB   rF   insiderG   real_bugr^   zsha-pr-tc05r_   z3expected GEMINI_REAL_BUG or GEMINI_UNRESOLVED, got z&expected GEMINI_UNRESOLVED_BLOCK, got ri   FN)r\   r   r0   rU   rj   rk   rm   r
   r   rr   r   ro   rn   )rV   rg   gemini_unresolvedrr   s       r"   +test_tc05_gemini_unresolved_real_bug_blocksr   M  s     +C *4>XY
 5>XY
 %
 "!^,%&	H !!o7H%II 
=h>T>T=UVI  77 
01B1B0C:hooM^_7 &&%///r$   c           
        t               }ddddgdddgdt        d}t        d| dt        t              t
        t        ||	      }|j                  t        k(  sJ d
|j                          |j                  t        k(  sJ d|j                          |j                  t        k(  sJ d|j                          |j                  du sJ y)u   Gemini critical_scope_expansion → BLOCKED_WITH_REASON +
    critical_code=CRITICAL_GEMINI_SCOPE_EXPANSION + classify→GEMINI_SCOPE_EXPANSION.critical_scope_expansionzutils/unrelated_file.pyz$fix scope expansion outside expectedr   critical_escalation_reporter)rB   rF   outsiderG   critical_coder^   zsha-pr-tc06r_   z%expected GEMINI_SCOPE_EXPANSION, got r   z<expected critical_code=CRITICAL_GEMINI_SCOPE_EXPANSION, got FN)r\   r   r   r0   rU   rj   rk   rm   r   rr   r   r   rn   )rV   rg   gemini_scoperr   s       r"   )test_tc06_gemini_scope_expansion_criticalr   w  s	    +C -.8^_
 /8^_
 /8
L "!^,%!	H !!%;; 
/0F0F/GH;  33 
,X->->,?@3 !!%DD 
FxG]G]F^_D &&%///r$   c                     t        d      } t        t        ddd      }t        |d| d d	      }|j                  t        k(  s!J d
|j                   d|j
                          t        |j
                  xs dvsJ d|j
                          y)u   smoke_command=None + dry_run=True 시:
    verify_head_lock_then_merge가 BLOCK하지 않고 dry-run 결과 그대로 통과.
    회장 §5: dry_run=True + None → WARN/SKIP 허용.Nry   r^   rI   sha-locked-tc07rr   r`   rL   pr_head_sha_startc                     y)Nr   r+   _ns    r"   <lambda>z9test_tc07_dry_run_without_smoke_allowed.<locals>.<lambda>      r$   Trr   r`   rg   fetch_pr_head_at_mergedry_runz6dry_run=True + smoke_command=None must NOT BLOCK, got ri    zEdry_run=True path must not raise NON_DRY_RUN_REQUIRES_SMOKE_COMMAND: )r\   r   r   r   rr   ro   r   rg   rr   results      r"   'test_tc07_dry_run_without_smoke_allowedr     s     $
'C#+	H );F ??00 
@@QQ[\b\i\i[jk0 .fmm6IrJ 
OPVP]P]_Jr$   c                    t        d      } t        t        ddd      }t        |d| d d	      }|j                  t
        k(  sJ d
|j                          t        |j                  xs dv sJ d|j                          y)u   smoke_command=None + dry_run=False → BLOCK + NON_DRY_RUN_REQUIRES_SMOKE_COMMAND.
    회장 §5: 자동 머지를 실제 수행하려면 smoke_command 정의 필수.Nry   r^   rI   sha-locked-tc08r   c                     y)Nr   r+   r   s    r"   r   z<test_tc08_non_dry_run_without_smoke_blocks.<locals>.<lambda>  r   r$   Fr   r   r   z8reason must contain NON_DRY_RUN_REQUIRES_SMOKE_COMMAND: )r\   r   r   r   rr   r   r   ro   r   s      r"   *test_tc08_non_dry_run_without_smoke_blocksr     s     $
'C#+	H );F ??11 
,V__,=>1 .&--2E2F 
B6==/RFr$   c           
     F   g d}t        |      }dg}t        |      | _        dg dddigd}t        d	| d
t        |      t        t
        ||      }|j                  t        k(  sJ d|j                          |j                  du sJ |j                  xs i j                  di       }d|v s
J d|        t        |      }d|v s
J d|        t        |j                  d      t              s
J d|        t        |      t        k(  sJ t        dg      t        k(  sJ y)u   utils/merge_queue_executor.py 변경 시 risk_level=HIGH_CORE.
    Gemini quota 미가용 fallback 시 static_risky_pattern_scan이 실행되고,
    fallback_check_details.checks.static_risky_scan_pass_if_high_core 항목 존재.rv   ry   r:   rz   Nr{   r|   r}   r^   zsha-pr-tc09r_   z#expected RISK_LEVEL_HIGH_CORE, got Tchecks#static_risky_scan_pass_if_high_corezPfallback_check_details.checks must include static_risky_scan_pass_if_high_core: passedz$scan result must have 'passed' key: 
violationszscan.violations must be list: zdocs/readme.md)r\   r0   rM   r   rj   rk   
risk_levelr   rp   rq   getr   
isinstancer   r   )rV   r   rg   high_core_filesr   rr   r   scans           r"   -test_tc09_high_core_static_risky_pattern_scanr     ss    ;I
)
,C 77O&*?&;# &?@A	L "!_-%!	H "66 
-h.A.A-BC6 ((D000--3882FF0F: 
Z[aZbc:
 %_5DtJCD6JJdhh|,d3 
(/3
 _-1EEEE./0NBBBr$   c                 8   t        j                  dddddigd      } t        dt        |       i      }d	dgd
g}t	        ||      }t        |      dk(  sJ |d   }|d   d	k(  sJ |d   du s
J d|        |d   du s
J d|        |d   du sJ |d   du sJ y)uC   후행 PR이 BEHIND 상태일 때 needs_recheck=True + behind=True.BEHINDzsha-behind-100r=   r   
utils/x.pyr>   r?   r@   files)100r   d   )r`   rM      r   r`   behindTzbehind must be True: needs_recheckzneeds_recheck must be True: conflictFblockedNjsondumpsr9   r#   r   len)behind_payloadr7   queuestatesstates        r"   %test_tc10_recheck_following_pr_behindr     s    ZZ$&<()	! N "N+ F <.ABE"5&1Fv;!1IE$$$?d"C&;E7$CC"!T)Q-I%+QQ)%%%u$$$r$   c                    t        j                  dddddiddigd      } t        dt        | 	      i      }d
dgdgdg}t	        ||      }t        |      dk(  sJ |d   }|d   d
k(  sJ |d   du s
J d|        |d   du s
J d|        y)u  후행 PR의 effective_files가 expected에서 벗어났을 때(또는 prior와 달라졌을 때)
    needs_recheck=True 또는 contamination 신호 감지.

    state machine이 신규 필드(effective_diff_drift / expected_files_maintained /
    forbidden_path_present)를 모두 포함하지 않아도 최소한 needs_recheck=True 또는
    DIRTY/conflict 신호로 재검증 필요를 표현해야 한다.DIRTYzsha-101r=   r   r   z
utils/y.pyr   )101r   e   )r`   rM   prior_effective_filesr   r   r`   r   Tu/   contamination 후행 PR은 needs_recheck=True: r   u   DIRTY → conflict=True: Nr   )contaminated_payloadr7   r   r   r   s        r"   )test_tc11_recheck_following_pr_diff_driftr   5  s      ::#\"\"
	'  "01 F
 +n&2^	
E #5&1Fv;!1IE$$$!T) 
9%A) $I(A%&II$r$   c                   ddl m} | dz  }| dz  }|j                  dd       |j                  dd       |dz  }|j                  |d|       |j                  |d	|       |j                  |d
|       t	        t
        ddt        ddt        dt
        ddddid      }|j                  |dd      }|J d       t        |      }|j                         s
J d|        t        j                  |j                  d            }	g d}
|
D ]*  }||	v rJ d| dt        |	j                                        |	d   t        k(  sJ |	d   du sJ |	d   du sJ |	d   t        k(  sJ |	d   du sJ |	d   t
        k(  sJ |	d   J |j                         s
J d |        |j                  d      j!                         d!   }t        j                  |      }|
D ]*  }||v rJ d"| dt        |j                                        y)#uo  write_audit 출력 JSON에 신규 7 필드(gemini_status, fallback_review_used,
    fallback_review_passed, risk_level, review_gate_passed, final_decision,
    critical_escalation)가 포함돼야 한다.

    글로벌 audit 로그가 더럽혀지지 않도록 EVENTS_DIR/AUDIT_DIR/GLOBAL_AUDIT_LOG를
    monkeypatch로 tmp_path 하위 디렉토리로 격리한다.r   NeventsauditT)parentsexist_okzmerge-queue.jsonl
EVENTS_DIR	AUDIT_DIRGLOBAL_AUDIT_LOGr^   rI   effective_diff_equals_expected)r   r   )rr   r`   rL   rm   rp   r   r   rn   final_decisioncritical_escalationrq   F)rL   rY   z2write_audit must return a path when no_audit=Falsez per-task audit must be written: zutf-8)encoding)	rm   rp   r   r   rn   r   r   rq   static_scan_violationsz#audit JSON missing required field 'z': keys=rm   rp   r   r   rn   r   r   z#global audit log must be appended: z"global audit jsonl missing field ')utils.merge_queue_executormerge_queue_executormkdirsetattrr   r   r   r   write_auditr   existsr   loads	read_textr0   keys
splitlines)tmp_pathmonkeypatchmq
events_dir	audit_dir
global_logrr   
audit_pathper_task_pathdatarequired_fieldsfield	last_lineglobal_datas                 r"   %test_tc12_audit_includes_7_new_fieldsr   `  s    ,H$J7"ITD1OOD4O000JL*5K3.
;#.!#') *.;[]a:bcH -%PJ!W#WW! $M!U%Em_#UU!::m--w-?@DO ! 
} 	
1%diikAR@ST	
}
  $<<<<&'4///()T111!5555$%--- !%7777%&... R"Ej\ RR$$g$6AACBGI**Y'K  
# 	
0x[EUEUEW@X?YZ	
#
r$   )r   r   r   )r   intr   strr   r   returnzsubprocess.CompletedProcessr*   )r6   zdict | None)r   r   )NNNzmain-sha-fixture-001N)rX   z
str | NonerZ   r   r   r   )rV   r   r   None)r   r  )r   r   r   zpytest.MonkeyPatchr   r  )=__doc__
__future__r   r   r    syspathlibr   rx   __file__resolveparent	WORKSPACEr   r   removeinsertr   r   r   r   r   r	   r
   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r#   r9   rU   rj   rk   rl   fixturerV   r\   rt   r   r   r   r   r   r   r   r   r   r   r   r+   r$   r"   <module>r     sw  ( #   
   N""$++2299	y>SXXHHOOC	N# 3y> "     8e
. $F  !  )R@2t<	   !2 	 .!P4t*`&X$0T%0VD>2Cp%4%JVJ
r$   