
    i !                    @   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ZddlZddlmZ ddlZ ee      j#                         j$                  d   Zedz  Zedz  Z ej,                  d	      d
        Zd Zd Zd Zd Zd Zd Zd Zd Zd Z d Z!d Z"d Z#d Z$d Z%d Z&y)u"  tests/lifecycle_guards/test_pre_push_guard.py — task-2471 회귀 테스트.

토르가 commit 0750481e 에서 ``scripts/pre_push_guard.py`` 에 적용한 4결함
수정을 영구 차단한다.

1. ``_parse_allowed_resources_yaml`` 의 numbered heading 인식
   (``## 7. allowed_resources`` 형식 + 기존 ``## allowed_resources`` 모두 OK)
2. ``_strip_yaml_inline_comment`` 헬퍼 (inline ``# 주석`` 제거)
3. ``extract_task_id_from_branch`` 헬퍼 (+N suffix 보존)
4. ``_resolve_allowed_resources`` capability snapshot + task 파일 모두 부재 시
   ``(None, error_msg)`` 반환 (실제 sys.exit 는 main 에서)

본 테스트는 ``tests/scripts/test_pre_push_guard.py`` (기존 7건 시나리오) 와
별도. 정적 단위 테스트만 수행.

헤임달(개발2팀 테스터) 작성.
    )annotationsN)Path   scriptszpre_push_guard.pymodule)scopec                    t         j                  }  |        }|st        j                  dt                dz   dt	        j
                         v st        j                  t               rt        j                  t               ndt        j                  |       t        j                  |      dz  }t        t        j                  |            dx} }t        t              t        t              fD ]5  }|t        j                  vst        j                  j                  d|       7 t         j"                  j%                  dt        t                     }g } d}||u}|}|r|j&                  }d}||u}	|	}|slt        j(                  d|fd	||f      d
t	        j
                         v st        j                  |      rt        j                  |      nd
t        j                  |      dz  }
dd|
iz  }| j+                  |       |rt        j(                  d	fdf      d
t	        j
                         v st        j                  |      rt        j                  |      nd
t        j                  |      t        j                  |      dz  }dd|iz  }| j+                  |       t        j,                  | d      i z  }dd|iz  }t        t        j                  |            dx}x} x}x}x}x}	}t         j"                  j/                  |      }|t        j0                  |j2                  <   |j&                  j5                  |       |S )u2  ``scripts/pre_push_guard.py`` 를 절대 경로 로드.

    ``pre_push_guard`` 자체가 ``sys.path`` 에 scripts/ 와 workspace root 를
    추가하므로 외부 setup 불필요. 단, qc_report_guard / task_scope 가 같은
    scripts 디렉토리에 있어야 함 (worktree 안에서 보장됨).
    z	missing: zC
>assert %(py4)s
{%(py4)s = %(py2)s
{%(py2)s = %(py0)s.exists
}()
}PRE_PUSH_GUARD_PATH)py0py2py4Nr   pre_push_guard_test_aliasis not)z%(py2)s is not %(py5)sspec)r   py5%(py7)spy7)z5%(py11)s
{%(py11)s = %(py9)s.loader
} is not %(py14)s)py9py11py14z%(py16)spy16zassert %(py19)spy19)r
   exists
@pytest_ar_format_assertmsg@py_builtinslocals_should_repr_global_name	_safereprAssertionError_format_explanationstr	WORKSPACESCRIPTS_DIRsyspathinsert	importlibutilspec_from_file_locationloader_call_reprcompareappend_format_boolopmodule_from_specmodulesnameexec_module)@py_assert1@py_assert3@py_format5pr   @py_assert4@py_assert0@py_assert10@py_assert13@py_assert12@py_format6@py_format8@py_format15@py_format17@py_format18@py_format20mods                    A/home/jay/workspace/tests/lifecycle_guards/test_pre_push_guard.pyppgrE       s    %%J%'J'JJ95H4I)JJJJJJJJJJJJJ%JJJ'JJJJJJ )nc+./ "CHHHHOOAq!" >>11#S)<%=D 8t74t77474 777774t77777747774777t77777774777777777777777477777777777777
..
)
)$
/C CKK		KKC J    c                   d}| j                  |      }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
}|d   }||v }|slt        j                  d|fd||f      t        j                  |      t        j                  |      dz  }	dd|	iz  }
t        t        j                  |
            dx}x}}d}|d   }||v }|slt        j                  d|fd||f      t        j                  |      t        j                  |      dz  }	dd|	iz  }
t        t        j                  |
            dx}x}}d}|d   }||v }|slt        j                  d|fd||f      t        j                  |      t        j                  |      dz  }	dd|	iz  }
t        t        j                  |
            dx}x}}y)u=   ``## 7. allowed_resources`` 형식 YAML 블록 정상 파싱.uz   # 헤더

## 7. allowed_resources
```yaml
paths:
  - "scripts/foo.py"
  - tests/bar.py
forbidden_paths:
  - secret/**
```
Nr   z%(py0)s is not %(py3)soutr   py3u   numbered heading 미인식
>assert %(py5)sr   scripts/foo.pypathsinz%(py1)s in %(py4)spy1r   assert %(py6)spy6ztests/bar.py	secret/**forbidden_paths)
_parse_allowed_resources_yamlr   r-   r   r   r   r    r   r!   r"   rE   textrI   @py_assert2r4   @py_format4r=   r9   r5   r6   @py_format7s              rD   %test_yaml_numbered_heading_recognizedr^   >   sq   	 	 
+
+D
1C83d?8883d88888838883888d8888888888+s7|+|++++|++++++|+++++++)S\)>\))))>\)))>)))\)))))))0#/00;00000;0000;00000000000rF   c                v   d}| j                  |      }d}||u}|st        j                  d|fd||f      dt        j                         v st        j
                  |      rt        j                  |      ndt        j                  |      dz  }dd|iz  }t        t        j                  |            dx}}|d	   }d
g}||k(  }|slt        j                  d|fd||f      t        j                  |      t        j                  |      dz  }	dd|	iz  }
t        t        j                  |
            dx}x}}y)uJ   기존 ``## allowed_resources`` heading 도 그대로 파싱 (regression).z7## allowed_resources
```yaml
paths:
  - utils/x.py
```
Nr   rH   rI   rJ   assert %(py5)sr   rN   
utils/x.py==z%(py1)s == %(py4)srR   rT   rU   	rX   r   r-   r   r   r   r    r!   r"   rY   s              rD   #test_yaml_plain_heading_still_worksrf   R   s    	 	 
+
+D
1C3d?3d33dw<)L>)<>))))<>)))<)))>)))))))rF   c                v   d}| j                  |      }d}||u}|st        j                  d|fd||f      dt        j                         v st        j
                  |      rt        j                  |      ndt        j                  |      dz  }dd|iz  }t        t        j                  |            dx}}|d	   }d
g}||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<   ``## 12. allowed_resources`` 처럼 두 자리 숫자도 OK.z5## 12. allowed_resources
```yaml
paths:
  - a.py
```
Nr   rH   rI   rJ   r`   r   rN   za.pyrb   rd   rR   rT   rU   re   rY   s              rD   $test_yaml_numbered_two_digit_headingrh   `   s    	 	 
+
+D
1C3d?3d33dw<#F8#<8####<8###<###8#######rF   c                r   | j                  d      }d}||k(  }|st        j                  d|fd||f      dt        j                         v st        j
                  |      rt        j                  |      ndt        j                  |      dz  }dd|iz  }t        t        j                  |            d	x}}y	)
u/   ``path/to/file  # 주석`` -> ``path/to/file``.u   path/to/file  # 주석path/to/filerb   z%(py0)s == %(py3)srI   rJ   r`   r   N	_strip_yaml_inline_commentr   r-   r   r   r   r    r!   r"   rE   rI   r[   r4   r\   r=   s         rD   test_strip_inline_comment_basicro   s   sl    

(
()A
BC  3.    3.      3   3   .       rF   c                r   | j                  d      }d}||k(  }|st        j                  d|fd||f      dt        j                         v st        j
                  |      rt        j                  |      ndt        j                  |      dz  }dd|iz  }t        t        j                  |            d	x}}y	)
u   quote 없는 단순 path.zpath  # commentr'   rb   rk   rI   rJ   r`   r   Nrl   rn   s         rD   "test_strip_inline_comment_no_quoterq   y   sk    

(
():
;C3&=3&33&rF   c                r   | j                  d      }d}||k(  }|st        j                  d|fd||f      dt        j                         v st        j
                  |      rt        j                  |      ndt        j                  |      dz  }dd|iz  }t        t        j                  |            dx}}y)	u"   주석 없으면 원본 그대로.rj   rb   rk   rI   rJ   r`   r   Nrl   rn   s         rD   .test_strip_inline_comment_no_comment_unchangedrs      sk    

(
(
8C  3.    3.      3   3   .       rF   c                   d}| j                  |      }d}||u}|st        j                  d|fd||f      dt        j                         v st        j
                  |      rt        j                  |      ndt        j                  |      dz  }dd|iz  }t        t        j                  |            dx}}d	}|d
   }||v }|slt        j                  d|fd||f      t        j                  |      t        j                  |      dz  }	dd|	iz  }
t        t        j                  |
            dx}x}}d}|d
   }||v }|slt        j                  d|fd||f      t        j                  |      t        j                  |      dz  }	dd|	iz  }
t        t        j                  |
            dx}x}}|d
   D ]  }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   }||v }|slt        j                  d|fd||f      t        j                  |      t        j                  |      dz  }	dd|	iz  }
t        t        j                  |
            dx}x}}y)u<   전체 YAML 파싱 흐름에서 inline 주석 제거 확인.u   ## allowed_resources
```yaml
paths:
  - "scripts/foo.py"  # 본 task 핵심
  - utils/bar.py    # 헬퍼
forbidden_paths:
  - secret/**  # ABSOLUTE forbidden
```
Nr   rH   rI   rJ   r`   r   rM   rN   rO   rQ   rR   rT   rU   zutils/bar.py#)not in)z%(py1)s not in %(py3)sr7   )rS   rK   u"   inline 주석이 path 에 잔존: rL   rV   rW   )
rX   r   r-   r   r   r   r    r!   r"   r   )rE   rZ   rI   r[   r4   r\   r=   r9   r5   r6   r]   r7   s               rD   $test_yaml_block_with_inline_commentsrw      s   	 	 
+
+D
1C3d?3d33d+s7|+|++++|++++++|+++++++)S\)>\))))>\)))>)))\)))))))\ HGs!|GGGs!GGGsGGGGGG!GGG!GGGGA!GGGGGGGH0#/00;00000;0000;00000000000rF   c                r   | j                  d      }d}||k(  }|st        j                  d|fd||f      dt        j                         v st        j
                  |      rt        j                  |      ndt        j                  |      dz  }dd|iz  }t        t        j                  |            d	x}}y	)
z-``task/task-2467+3-dev6`` -> ``task-2467+3``.ztask/task-2467+3-dev6ztask-2467+3rb   rk   rI   rJ   r`   r   N	extract_task_id_from_branchr   r-   r   r   r   r    r!   r"   rn   s         rD   %test_branch_parsing_with_retry_suffixr{      sl    

)
)*A
BC3-3-33-rF   c                p   | j                  d      }d}||u }|st        j                  d|fd||f      dt        j                         v st        j
                  |      rt        j                  |      ndt        j                  |      dz  }dd|iz  }t        t        j                  |            dx}}y)	u)   ``main`` 같은 일반 브랜치는 None.mainNisz%(py0)s is %(py3)srI   rJ   r`   r   ry   rn   s         rD   %test_branch_parsing_main_returns_noner      sj    

)
)&
1C3$;3$33$rF   c                r   | j                  d      }d}||k(  }|st        j                  d|fd||f      dt        j                         v st        j
                  |      rt        j                  |      ndt        j                  |      dz  }dd|iz  }t        t        j                  |            d	x}}y	)
u'   phase + parallel + retry 모두 보존.ztask/task-2470_1.2_a+5-dev3ztask-2470_1.2_a+5rb   rk   rI   rJ   r`   r   Nry   rn   s         rD   -test_branch_parsing_full_phase_parallel_retryr      so    

)
)*G
HC%%3%%%%%3%%%%%%%3%%%3%%%%%%%%%%%rF   c                   | j                   }d} ||      }d }||u }|st        j                  d|fd||f      dt        j                         v st        j
                  |       rt        j                  |       ndt        j                  |      t        j                  |      t        j                  |      t        j                  |      dz  }dd|iz  }t        t        j                  |            d x}x}x}x}}y )N r~   )za%(py6)s
{%(py6)s = %(py2)s
{%(py2)s = %(py0)s.extract_task_id_from_branch
}(%(py4)s)
} is %(py9)srE   )r   r   r   rU   r   zassert %(py11)sr   ry   )rE   r4   r5   @py_assert5@py_assert8@py_assert7@py_format10@py_format12s           rD   test_branch_parsing_empty_inputr      s    **626*2.6$6.$6666.$66666636663666*6662666.666$66666666rF   c                   | j                  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  }dd|iz  }t        t        j                  |            dx}}|sed	d
dt	        j
                         v st        j                  |      rt        j                  |      ndiz  }t        t        j                  |            g }d}||v }	|	}
|	sd}|j                  } |       }||v }|}
|
st        j                  d|	fd||f      t        j                  |      dt	        j
                         v st        j                  |      rt        j                  |      nddz  }dd|iz  }|j                  |       |	st        j                  dfdf      t        j                  |      dt	        j
                         v st        j                  |      rt        j                  |      ndt        j                        t        j                  |      dz  }dd|iz  }|j                  |       t        j                  |d      i z  }dd|iz  }t        t        j                  |            dx}
x}x}x}	x}x}x}}y)uQ   capability snapshot 부재 + task 파일 부재 → ``(None, error_msg)`` 반환.z	task-9999Nr~   r   arrJ   r`   r   assert %(py0)sr   erru   없음notrO   )z%(py3)s in %(py5)s)rK   r   r   r   )zJ%(py10)s in %(py16)s
{%(py16)s = %(py14)s
{%(py14)s = %(py12)s.lower
}()
})py10py12r   r   z%(py18)spy18   zassert %(py21)spy21)_resolve_allowed_resourcesr#   r   r-   r   r   r   r    r!   r"   lowerr.   r/   )rE   tmp_pathr   r   r[   r4   r\   r=   @py_format1r8   r9   @py_assert9r;   @py_assert15@py_assert11r>   r@   @py_format19rB   @py_format22s                       rD   <test_resolve_allowed_resources_returns_none_when_both_absentr      s    ,,[#h-HGB2:222JJJJJ3JJJ3JJJJJ2828s?2e2syy2y{2e{222228s2228222222s222s2222222e{222e222222s222s222y222{22222222222222rF   c                f   |dz  dz  }|j                  d       ddgdgdi}|d	z  j                  t        j                  |             | j	                  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  }d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  }dd|iz  }	t        t        j                  |	            dx}}d}
|d   }|
|v }|slt        j                  d|fd|
|f      t        j                  |
      t        j                  |      dz  }dd|iz  }t        t        j                  |            dx}
x}}d}
|d   }|
|v }|slt        j                  d|fd|
|f      t        j                  |
      t        j                  |      dz  }dd|iz  }t        t        j                  |            dx}
x}}y)uL   capability snapshot 이 있으면 그것을 사용 (task 파일 fallback X).memorycapabilitiesTparentsallowed_resourcesrM   rV   )rN   rW   ztask-2471.jsonz	task-2471r   rb   rk   r   rJ   r`   r   Nr   rH   r   rN   rO   rQ   rR   rT   rU   rW   )mkdir
write_textjsondumpsr   r#   r   r-   r   r   r   r    r!   r"   )rE   r   cap_dirsnapr   r   r[   r4   r\   r=   r9   r5   r6   r]   s                 rD   7test_resolve_allowed_resources_uses_capability_snapshotr      s   !N2GMM$M&' +}
D ++DJJt,<=,,[#h-HGB3"93"33"2T>2T22T*r'{*{****{******{*******/".//;/////;////;///////////rF   c                0   |dz  dz  }|j                  d       |dz  j                  d       | j                  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  }d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  }dd|iz  }t        t	        j                  |            dx}}d}	|d   }
|	|
v }|slt	        j
                  d|fd|	|
f      t	        j                  |	      t	        j                  |
      dz  }dd|iz  }t        t	        j                  |            dx}	x}}
y)uD   capability snapshot 없으면 task .md 파일의 YAML 블록 사용.r   tasksTr   ztask-9000.mdzM# task-9000

## 5. allowed_resources
```yaml
paths:
  - utils/x.py  # OK
```
z	task-9000r   rb   rk   r   rJ   r`   r   Nr   rH   r   ra   rN   rO   rQ   rR   rT   rU   r   r   r   r#   r   r-   r   r   r   r    r!   r"   )rE   r   	tasks_dirr   r   r[   r4   r\   r=   r9   r5   r6   r]   s                rD   6test_resolve_allowed_resources_falls_back_to_task_filer      sQ   8#g-IOODO!++	 ,,[#h-HGB3"93"33"2T>2T22T&2g;&<;&&&&<;&&&<&&&;&&&&&&&rF   c                   |dz  dz  }|j                  d       |dz  j                  d       | j                  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  }dd|iz  }t        t	        j                  |            dx}}|sedddt        j                         v st	        j                  |      rt	        j                  |      ndiz  }	t        t	        j                  |	            y)uW   task .md 파일은 있지만 ``## allowed_resources`` 블록 없으면 ``(None, msg)``.r   r   Tr   ztask-7777.mdu,   # task

본문만 있고 YAML 블록 없음
z	task-7777Nr~   r   r   rJ   r`   r   r   r   r   r   )
rE   r   r   r   r   r[   r4   r\   r=   r   s
             rD   ;test_resolve_allowed_resources_task_file_without_yaml_blockr      s    8#g-IOODO!++,]^,,[#h-HGB2:222JJJJJ3JJJ3JJJJJ3rF   )'__doc__
__future__r   builtinsr   _pytest.assertion.rewrite	assertionrewriter   importlib.utilr)   r   r&   pathlibr   pytest__file__resolver   r$   r%   r
   fixturerE   r^   rf   rh   ro   rq   rs   rw   r{   r   r   r   r   r   r   r    rF   rD   <module>r      s   " #      
  N""$,,Q/	)#!$77  h  :1(*$&!!16 &730&'&rF   