
    #j3                    J   d Z ddlmZ ddlZddlZddlZddlZddlZddlm	Z	 ddl
mZmZ dddZdddZddZdd	Zddd
ZdddZd dZd!dZ e	e      j,                  j,                  dz  dz  dz  dz  Zg dZd"dZd#dZd#dZedk(  r ej:                   e              yy)$u]  
verify_workflow_sha_payload.py

두 모드 지원:
  --mode resolve   : GitHub Actions workflow runtime 에서 SHA/PR 추출 (fallback chain + canonical 검증)
  --mode dry-run   : tests/regression/fixtures/ 의 fixture 4종으로 자체 검증

작성자: 불칸 (dev1-team 백엔드)
task: task-2486 — CI pull_request SHA payload fallback fix
    )annotationsN)Path)OptionalTuplec                ^    t         j                  j                  | |      j                         S )N)osenvirongetstrip)keydefaults     W/home/jay/workspace/.worktrees/task-2729+10-dev6/scripts/verify_workflow_sha_payload.py_envr      s     ::>>#w'--//    c                    	 t        j                  dg| z   dd|      }|j                  dk(  r|j                  j	                         S y# t
        $ r Y yw xY w)u3   gh CLI 호출. 실패/타임아웃 시 None 반환.ghT)capture_outputtexttimeoutr   N)
subprocessrun
returncodestdoutr   	Exception)argsr   results      r   _run_ghr       s_    FTM	
 !==&&(( s   AA	 		AAc                    dj                  |       dz   }|r*	 t        |dd      5 }|j                  |       ddd       yt        j
                  j                  |       y# 1 sw Y   yxY w# t        $ r Y 7w xY w)uP   $GITHUB_STEP_SUMMARY 에 markdown 라인을 append. 미설정 시 stderr 출력.
autf-8encodingN)joinopenwriteOSErrorsysstderr)linesstep_summary_pathcontentfs       r   _emit_auditr.   0   ss    ii%G	'w? !1 ! JJW! 		s-   A- A!A- !A*&A- *A- -	A98A9c                    | si S 	 t        | dd      5 }t        j                  |      cd d d        S # 1 sw Y   y xY w# t        $ r i cY S w xY w)Nrr!   r"   )r%   jsonloadr   )
event_pathr-   s     r   _load_eventr4   =   sP    	*cG4 	 99Q<	  	  	  	s#   ? 3	? <? ? AAc           	        g }t        | j                  di       j                  di       j                  d      xs d      j                         }|j                  |ddf       t        | j                  d      xs d      j                         }|j                  |dd	f       t	        d
      }|j                  |dd	f       d}|sJ|rHt        dd|ddd|g      }	|	r4	 t        j                  |	      j                  dd      j                         }|j                  |ddf       |D ]  \  }
}}|
s
|
||fc S  y# t        $ r d}Y 6w xY w)u   
    SHA 추출 fallback chain.

    Returns:
        (sha_value, stage_used, is_canonical)
        stage_used: "1" | "2" | "3" | "4"
        is_canonical: True 이면 canonical 비교 불필요
    pull_requestheadsha 1Tafter2F
GITHUB_SHA3prview--json
headRefOid--repo4)r9   noneF)	strr
   r   appendr   r   r1   loadsr   )event	pr_numberrepodry_run
candidatessha1sha2sha3sha4rawsha_valstageis_cans                r   _resolve_sharV   K   s]    /1J uyy,00<@@GM2NTTVDtS$'( uyy!'R(..0DtS%() DtS%() DytVY,RVWXzz#**<<BBD tS$'(", *E6))*   s   3D9 9EEc                >   | j                  di       j                  d      }|)t        |      j                         }|r|dk7  r	|dk7  r|dfS | j                  d      }|)t        |      j                         }|r|dk7  r	|dk7  r|dfS |s|r|rt        ddd	|d
dddddd|g      }|rg	 t	        j
                  |      }t        |t              r@t        |      dk(  r2t        |d   j                  dd            j                         }	|	r|	dfS yy# t        $ r Y yw xY w)u   
    PR 번호 추출 fallback chain.

    Returns:
        (pr_number, stage_used)
        stage_used: "1" | "2" | "3" | "none"
    r6   numberNonenullr:   r<   r?   listz--headz--basemainz--stater%   rA   z&number,headRefName,headRepositoryOwnerrC      r   r9   r>   )r9   rE   )
r
   rF   r   r   r1   rH   
isinstancer[   lenr   )
rI   branchrK   rL   pr1valpr2rR   itemsnums
             r   _resolve_prrf   x   s5    ))NB
'
+
+H
5C
#hnn3&=SF]8O ))H
C
#hnn3&=SF]8O v$&ffv>d
  

3eT*s5zQeAhll8R89??AC"Cx
   s   )A$D 	DDc           	         t        dd|ddd|g      }|s| dfS 	 t        j                  |      j                  dd      j	                         }|s| dfS || k7  r|dfS | dfS # t
        $ r | dfcY S w xY w)	u   
    gh pr view 로 canonical SHA 조회, mismatch 시 canonical 로 교체.

    Returns:
        (final_sha, was_mismatch)
    r?   r@   rA   rB   rC   Fr9   T)r   r1   rH   r
   r   r   )r8   rJ   rK   rR   	canonicals        r   _verify_canonicalri      s     4HlHdS
TCEzJJsO''b9??A	 EzC$:  Ezs   3A   A0/A0c                `   | j                   xs t        d      }| j                  xs t        d      xs t        d      }| j                  xs t        d      }t        d      }t	        |      }t        |||      \  }}t        ||xs d |      \  }}	}
d}|r|
s|r|rt        |||      \  }}dddd	d
dt        |j                  di       j                  di       j                  d      xs d      d d  d|	dk(   ddt        |j                  d      xs d      d d  d|	dk(   ddt        d      d d  d|	dk(   dd|	dk(   dd t        |j                  di       j                  d!      xs d       d|dk(   dd"t        |j                  d!      xs d       d|dk(   dd#|dk(   d$g}|r&|j                  d       |j                  d%|	 d&       |j                  d       t        ||       |r|st        d'j                  ||             y(t        d)|        t        d*|        y+),NGITHUB_EVENT_PATHGITHUB_HEAD_REFGITHUB_REF_NAMEGITHUB_REPOSITORYGITHUB_STEP_SUMMARYFr9   z## SHA/PR Resolve Auditz,| Candidate | Value | Stage | Used | Notes |z,|-----------|-------|-------|------|-------|z'| SHA-1 event.pull_request.head.sha | `r6   r7   r8      z` | 1 | r:   z | primary |z| SHA-2 event.after | `r;   z` | 2 | r<   z | fallback |z| SHA-3 GITHUB_SHA | `r=   z` | 3 | r>   z | env fallback |z0| SHA-4 gh pr view headRefOid | canonical | 4 | rD   z | canonical |z$| PR-1 event.pull_request.number | `rX   z| PR-2 event.number | `z | PR-3 gh pr list | (api) | 3 | z | gh fallback |uK   > **WARNING**: SHA fallback mismatch — canonical override applied (stage=u   →canonical)z4::error::missing pull_request payload (SHA={} PR={})r]   zRESOLVED_SHA=zRESOLVED_PR=r   )r3   r   r`   rK   r4   rf   rV   ri   rF   r
   rG   r.   printformat)r   r3   r`   rK   step_summaryrI   rJ   pr_stager8   	sha_stagesha_is_canonicalsha_mismatchaudit_liness                r   mode_resolvery      s   =D)<$=J[[ND!23Nt<M7NF99101D-.L
#E &eVT:Ix (4E9;Ld'S$C$ L
#	d-c9dC\ 	!
66
1#eiiPR6S6W6WX^`b6c6g6ghm6n6trt2uvywy2z1{  |D  EN  RU  EU  DV  Vb  	c
!#eii&8&>B"?"D!EXi[^N^M__lm
 l!3CR!8 9)sBRASSde
:9;K:LN[
.s599^R3P3T3TU]3^3dbd/e.ffnow{~o~n  @L  	M
!#eii&9&?R"@!A(VY/IZZgh
*8s?*;;KLK 2hirhs  tA  B  	Cr\* iDKKCQZ[\ 
M#
 	L
$%r   tests
regressionfixturesworkflow_sha_payload)pr_event_normalpr_event_empty_shapr_event_empty_prpr_event_empty_bothc                .   t         |  dz  }t         |  dz  }|j                         sdd| fS |j                         sdd| fS t        |dd      5 }t        j                  |      }d	d	d	       t        |dd      5 }t        j                  |      }d	d	d	       j                  d
i       }i }|j                         D ]C  \  }}	t        j                  j                  |      ||<   t        |	      t        j                  |<   E 	 t        ddd      \  }
}t        ||
xs d	dd      \  }}}t        |xr |
      }|j                  dd      }|rdnd}||k7  rbdd|  d| d| d|d|
df|j                         D ];  \  }}|!t        j                  j                  |d	       )|t        j                  |<   = S |dk(  r|j                  d      }|j                  d      t        |j                  dd            nd	}|a||k7  r\dd|  d|d|df|j                         D ];  \  }}|!t        j                  j                  |d	       )|t        j                  |<   = S |a|
|k7  r\dd|  d|d|
df|j                         D ];  \  }}|!t        j                  j                  |d	       )|t        j                  |<   = S dd|  d|d|
d| f|j                         D ];  \  }}|!t        j                  j                  |d	       )|t        j                  |<   = S # 1 sw Y   xY w# 1 sw Y   xY w# |j                         D ];  \  }}|!t        j                  j                  |d	       )|t        j                  |<   = w xY w) u   
    단일 fixture 에 대해 resolve 로직을 dry-run (gh 호출 없이).

    Returns:
        (passed, detail_message)
    z.jsonz.expected.jsonFzFIXTURE NOT FOUND: zEXPECTED NOT FOUND: r0   r!   r"   Nenv_overridesr9   T)r`   rK   rL   )rK   rL   	exit_coder   r]   zFAIL [z ]: exit_code mismatch (expected=z, got=z, sha=z, pr=)r8   r?   z]: SHA mismatch (expected=z]: PR mismatch (expected=zPASS [z]: sha=z pr=z exit=)FIXTURES_DIRexistsr%   r1   r2   r
   rd   r   r	   rF   rf   rV   boolpop)name
event_fileexpected_filer-   rI   expectedr   original_envkvrJ   _r8   resolved_okexpected_exitgot_exitorigexp_shaexp_prs                      r   _run_fixture_dryrunr   	  s    4&.J dV>#::M+J<888!,]O<<<	j#	0 A		!	mS7	3  q99Q<  #,,;ML##% 1**..+QA

1+%"5"dK	1 !	(9TDQ	Q
 3,9- [!4#1 }$ *O6(6#i]Z[]. $))+ 	%GAt|

q$' $

1		%# All5)G4<LL4F4RSdB/0X\F"sg~TF"<WKvcWTUV $))+ 	%GAt|

q$' $

1		% !i6&9TF";F:VI=XYZ $))+ 	%GAt|

q$' $

1		%	 vdV73'i]&
SS $))+ 	%GAt|

q$' $

1		%g    b $))+ 	%GAt|

q$' $

1		%s9   L*L7
A(M AM )M M *L47MANc                 j   t        d       d} g }t        D ]1  }t        |      \  }}|j                  |       t        |       |r0d} 3 t                | r-t        dt	        t               dt	        t               d       yt        d |D              }t        d	| dt	        t               d
       y)Nz5=== verify_workflow_sha_payload.py --mode dry-run ===TFzdry-run result: PASS (/z
 fixtures)r   c              3  D   K   | ]  }|j                  d       sd  yw)FAILr]   N)
startswith).0r0   s     r   	<genexpr>zmode_dryrun.<locals>.<genexpr>c  s     Dqq||F/CDs     zdry-run result: FAIL (z fixtures failed)r]   )rq   DRY_RUN_FIXTURESr   rG   r_   sum)all_passresultsr   passeddetail
fail_counts         r   mode_dryrunr   R  s    	
ABHG  ,T2vfH 
G&s+;'<&=QsCS?T>UU_`aDGDD
&zl!C8H4I3JJ[\]r   c                    t        j                  d      } | j                  dddgdd       | j                  d	d
d       | j                  dd
d       | j                  dd
d       | j                  dd
d       | j                         }|j                  dk(  rt        |      S |j                  dk(  r
t               S t        d|j                          y)NuH   verify_workflow_sha_payload — SHA/PR fallback chain for GitHub Actions)descriptionz--moderesolvezdry-runTuC   실행 모드: resolve (workflow runtime) | dry-run (자체 검증))choicesrequiredhelpz--event-pathr9   u7   GitHub event JSON 경로 (기본값: GITHUB_EVENT_PATH))r   r   z--event-nameu/   이벤트 이름 (기본값: GITHUB_EVENT_NAME)z--branchuB   head 브랜치 (기본값: GITHUB_HEAD_REF 또는 GITHUB_REF_NAME)rC   u(   저장소 (기본값: GITHUB_REPOSITORY)z::error::unknown mode: r]   )argparseArgumentParseradd_argument
parse_argsmodery   r   rq   )parserr   s     r   r\   r\   l  s    $$^F I&R	   9rs
9jk

B5yz
"3]^DyyID!!	i	}'		{34r   __main__)r9   )r   rF   r   rF   returnrF   )   )r   	list[str]r   intr   Optional[str])r*   r   r+   rF   r   rY   )r3   rF   r   dict)F)
rI   r   rJ   r   rK   rF   rL   r   r   zTuple[str, str, bool])
rI   r   r`   rF   rK   rF   rL   r   r   zTuple[str, str])r8   rF   rJ   rF   rK   rF   r   zTuple[str, bool])r   zargparse.Namespacer   r   )r   rF   r   zTuple[bool, str])r   r   )__doc__
__future__r   r   r1   r   r   r(   pathlibr   typingr   r   r   r   r.   r4   rV   rf   ri   ry   __file__parentr   r   r   r   r\   __name__exit r   r   <module>r      s   	 #   	  
  "0 
&Z+d:1p H~$$++g5DzQTjj F%R44 zCHHTV r   