
    ¬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dZdddZ G d d      Z G d	 d
      Z G d d      Zy)ul  task-2457 Phase 2-A: pre-push hook 테스트.

명세:
  차단 케이스 (exit 1):
    1. main direct push (stdin: "refs/heads/x sha refs/heads/main sha") → "main direct push prohibited"
    2. cancelled marker (memory/events/<task-id>.cancelled 존재) → "task <id> cancelled"
    3. lock 부재 / branch-lock 불일치 → "lock missing" or "task-id mismatch"
    4. scope 위반 (변경 파일이 allowed_resources에 없음) → "scope violation"

  PASS 케이스 (exit 0):
    P3. lock + scope_check PASS + cancelled 아님 + main 아님
    P4. TASKCTL_BYPASS=1 + reason 제공 → bypass evidence 기록 + PASS

  Fallback 케이스:
    - taskctl verify 미존재 → fallback evidence 기록 (verify_status="fallback") + PASS
      파일: .tasks/evidence/<task-id>/verify-fallback-*.json
      필드: taskctl_verify_status / reason / lock_check / scope_check
    )annotationsN)Pathc                   | |z  }|j                   j                  dd       |j                  |d       t        j                  dd|gt        |       dd       t        j                  ddd	d
| dgt        |       dd       y)uM   헬퍼: repo 안에 파일을 만들고 --no-verify 로 commit (hook 우회).T)parentsexist_okutf-8encodinggitadd)cwdcheckcapture_outputcommitz-mzadd z--no-verifyN)parentmkdir
write_text
subprocessrunstr)reporel_pathcontentfs       4/home/jay/workspace/tests/git_hooks/test_pre_push.py_commit_filer      s{    xAHHNN4$N/LL7L+NNE5(+T$W[\NN	$$xj 1=AI	    c           	     ,    d}d}d}d| d| d|  d| d	S )u1   헬퍼: pre-push hook 표준 stdin 라인 생성.task/task-2457-dev3'abc1234abc1234abc1234abc1234abc1234abc1'def5678def5678def5678def5678def5678def5zrefs/heads/ z refs/heads/
 )remote_branchlocalsha_lsha_rs       r   
_stdin_forr)   *   s2    !E5E5Eq|M?!E7"MMr   c                  $    e Zd Zd Zd Zd Zd Zy)TestPrePushFailc                j   d} |||        ||d|       t        d      } ||d|      }|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|j                        dz   d|iz  }t        t        j                  |            dx}x}
}	d}|j                  }	||	v }|st        j                  d|fd||	f      t        j                  |      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}x}}	d}|j                  }	||	v }|st        j                  d|fd||	f      t        j                  |      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}x}}	y)u5   1) refspec 의 remote 가 refs/heads/main → 차단.r   	task-2457task_idbranchmain)r%   pre-pushstdin   ==z2%(py2)s
{%(py2)s = %(py0)s.returncode
} == %(py5)sresultpy0py2py5u*   main direct push 인데 통과됨. stdout= stderr=
>assert %(py7)spy7N	[BLOCKED]inz.%(py1)s in %(py5)s
{%(py5)s = %(py3)s.stderr
}py1py3r=   assert %(py7)szmain direct push prohibited)r)   
returncode
@pytest_ar_call_reprcompare@py_builtinslocals_should_repr_global_name	_saferepr_format_assertmsgstdoutstderrAssertionError_format_explanation)selfgit_repocheckout_branch	make_lockrun_hookr0   r4   r9   @py_assert1@py_assert4@py_assert3@py_format6@py_format8@py_assert0@py_assert2s                  r   test_main_direct_push_blocksz,TestPrePushFail.test_main_direct_push_blocks6   s    '&)(K?0(Je<   	
A 	
 A% 	
 	
 A 	
 	
	6	
 	
   	
 	
 		  	
 	
 		 ! 	
 	
 		 %& 	
 	
  98IRXR_R_Qbc	
 	
 	
 	
 	
 +fmm+{m++++{m+++{++++++f+++f+++m+++++++,==,====,===,===================r   c                   d} |||        ||d|        ||d        ||dt                     }|j                  }d}	||	k(  }
|
st        j                  d|
fd||	f      d	t	        j
                         v st        j                  |      rt        j                  |      nd	t        j                  |      t        j                  |	      d
z  }t        j                  d|j                  d|j                        dz   d|iz  }t        t        j                  |            dx}x}
}	d}|j                  }	||	v }|st        j                  d|fd||	f      t        j                  |      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}x}}	d}|j                  }	|	j                  } |       }||v }|st        j                  d|fd||f      t        j                  |      d	t	        j
                         v st        j                  |      rt        j                  |      nd	t        j                  |	      t        j                  |      t        j                  |      dz  }dd|iz  }t        t        j                  |            dx}x}x}	x}}d}|j                  }	||	v }|st        j                  d|fd||	f      t        j                  |      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}x}}	y)u&   2) cancelled marker 존재 → 차단.r   r-   r.   r2   r3   r5   r6   r8   r9   r:   u*   cancelled marker 인데 통과됨. stdout=r>   r?   r@   NrA   rB   rD   rE   rH   	cancelledz`%(py1)s in %(py9)s
{%(py9)s = %(py7)s
{%(py7)s = %(py5)s
{%(py5)s = %(py3)s.stderr
}.lower
}()
}rF   rG   r=   r@   py9assert %(py11)spy11)r)   rI   rJ   rK   rL   rM   rN   rO   rP   rQ   rR   rS   rT   lower)rU   rV   rW   rX   make_cancelledrY   r0   r9   rZ   r[   r\   r]   r^   r_   r`   @py_assert6@py_assert8@py_format10@py_format12s                      r   test_cancelled_marker_blocksz,TestPrePushFail.test_cancelled_marker_blocksE   s    '&)(K?x-(JjlC   	
A 	
 A% 	
 	
 A 	
 	
	6	
 	
   	
 	
 		  	
 	
 		 ! 	
 	
 		 %& 	
 	
  98IRXR_R_Qbc	
 	
 	
 	
 	
 +fmm+{m++++{m+++{++++++f+++f+++m+++++++3fmm3m113133{33333{3333{333333f333f333m333133333333333+fmm+{m++++{m+++{++++++f+++f+++m+++++++r   c                h   d} |||        ||dt                     }|j                  }d}||k(  }|st        j                  d|fd||f      dt	        j
                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      dz  }	t        j                  d	|j                  d
|j                        dz   d|	iz  }
t        t        j                  |
            dx}x}}d}|j                  }||v }|st        j                  d|fd||f      t        j                  |      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}x}}|j                  j                         }g }d}||v }|}|sd}||v }|}|sd}||v }|}|s
t        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                  |
       |st        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dz  }d d!|iz  }|j                  |       t        j                  |d      i z  }t        j                  d"|j                        d#z   d$|iz  }t        t        j                  |            dx}x}x}x}x}x}x}}y)%u   3) lock 부재 → 차단.r   r2   r3   r5   r6   r8   r9   r:   u$   lock 부재인데 통과됨. stdout=r>   r?   r@   NrA   rB   rD   rE   rH   zlock missingztask-id mismatchlock)z%(py3)s in %(py5)smsg)rG   r=   z%(py7)s)z%(py10)s in %(py12)s)py10py12z%(py14)spy14)z%(py17)s in %(py19)s)py17py19z%(py21)spy21u   lock 관련 메시지 부재: z
>assert %(py24)spy24)r)   rI   rJ   rK   rL   rM   rN   rO   rP   rQ   rR   rS   rT   ri   append_format_boolop)rU   rV   rW   rY   r0   r9   rZ   r[   r\   r]   r^   r_   r`   rr   @py_assert9@py_assert11@py_assert16@py_assert18@py_format13@py_format15@py_format20@py_format22@py_format23@py_format25s                           r   test_lock_missing_blocksz(TestPrePushFail.test_lock_missing_blocksZ   s|    '&)(JjlC   	
A 	
 A% 	
 	
 A 	
 	
	6	
 	
   	
 	
 		  	
 	
 		 ! 	
 	
 		 %& 	
 	
  36==2C8FMMK\]	
 	
 	
 	
 	
 +fmm+{m++++{m+++{++++++f+++f+++m+++++++mm!!#	
 	
#% 	
+= 	
+=+D 	
& 	
&TW- 	
 	
 	
# 	
 	
 		  	
 	
	6	
 	
  #& 	
 	
 		 #& 	
 	
 	
	6	
		
 	
+= 	
 	
 		 ,> 	
 	
	6	
 	
  BE 	
 	
 		 BE 	
 	
 	
	6	
		
 	
&TW 	
 	
 		 KQ 	
 	
	6	
 	
  UX 	
 	
 		 UX 	
 	
 	
	6	
		
 	
 	
  -V]],=>	
 	
 	
 	
 	
 	
 	
r   c                   d} |||        ||d|        ||ddg       t        |dd        ||dt               	      }|j                  }d
}	||	k(  }
|
st        j                  d|
fd||	f      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |	      dz  }t        j                  d|j                  d|j                        dz   d|iz  }t        t        j                  |            dx}x}
}	d}|j                  }	||	v }|st        j                  d|fd||	f      t        j                  |      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}x}}	d}|j                  }	|	j                  } |       }||v }|st        j                  d|fd||f      t        j                  |      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |	      t        j                  |      t        j                  |      dz  }dd|iz  }t        t        j                  |            dx}x}x}	x}}y)uH   4) capability 의 allowed_resources 에 없는 파일 변경 → 차단.r   r-   r.   tests/git_hooks/allowed_pathszscripts/forbidden.pyzprint('out of scope')
r2   r3   r5   r6   r8   r9   r:   u%   scope 위반인데 통과됨. stdout=r>   r?   r@   NrA   rB   rD   rE   rH   zscope violationrd   re   rg   rh   )r   r)   rI   rJ   rK   rL   rM   rN   rO   rP   rQ   rR   rS   rT   ri   )rU   rV   rW   rX   make_capabilityrY   r0   r9   rZ   r[   r\   r]   r^   r_   r`   rk   rl   rm   rn   s                      r   test_scope_violation_blocksz+TestPrePushFail.test_scope_violation_blocksk   s8    '&)(K?-.	
 	X57PQ(JjlC   	
A 	
 A% 	
 	
 A 	
 	
	6	
 	
   	
 	
 		  	
 	
 		 ! 	
 	
 		 %& 	
 	
  4FMM3DHV]]L]^	
 	
 	
 	
 	
 +fmm+{m++++{m+++{++++++f+++f+++m+++++++ 9FMM9M$7$79$7$99 $99999 $9999 999999F999F999M999$7999$999999999r   N)__name__
__module____qualname__ra   ro   r   r   r$   r   r   r+   r+   5   s    >,*
":r   r+   c                      e Zd Zd Zd Zy)TestPrePushPassc                z   d} |||        ||d|        ||dddg       t        |dd        ||d	t               
      }|j                  }d}	||	k(  }
|
st        j                  d|
fd||	f      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |	      dz  }t        j                  d|j                  d|j                        dz   d|iz  }t        t        j                  |            dx}x}
}	y)uB   P3) lock + scope PASS + cancelled 아님 + main 아님 → exit 0.r   r-   r.   r   zscripts/git-hooks/r   ztests/git_hooks/sample.txtok
r2   r3   r   r6   r8   r9   r:   u)   정상 케이스인데 차단됨. stdout=r>   r?   r@   N)r   r)   rI   rJ   rK   rL   rM   rN   rO   rP   rQ   rR   rS   rT   )rU   rV   rW   rX   r   rY   r0   r9   rZ   r[   r\   r]   r^   s                r   test_valid_lock_scope_passesz,TestPrePushPass.test_valid_lock_scope_passes   s2    '&)(K?-/CD	
 	X;VD(JjlC   	
A 	
 A% 	
 	
 A 	
 	
	6	
 	
   	
 	
 		  	
 	
 		 ! 	
 	
 		 %& 	
 	
  87HQWQ^Q^Pab	
 	
 	
 	
 	
 	
r   c                L   d} |||       ddd} ||dt               |      }|j                  }d}||k(  }	|	st        j                  d|	fd	||f      d
t	        j
                         v st        j                  |      rt        j                  |      nd
t        j                  |      t        j                  |      dz  }
t        j                  d|j                  d|j                        dz   d|
iz  }t        t        j                  |            dx}x}	}|dz  dz  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                  d            }|st        j                  dt        |j!                                      dz   ddt	        j
                         v st        j                  |      rt        j                  |      ndiz  }t        t        j                  |            t#        j$                  |d   j'                  d            }|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}}|j(                  }d'}	 ||	      }|sd(d#t	        j
                         v st        j                  |      rt        j                  |      nd#t        j                  |      t        j                  |	      t        j                  |      d)z  }t        t        j                  |            dx}x}	}|j(                  }d*}	 ||	      }t*        j,                  }|j(                  }d+} ||      }||k(  }|sZt        j                  d|fd,||f      d#t	        j
                         v st        j                  |      rt        j                  |      nd#t        j                  |      t        j                  |	      t        j                  |      d-t	        j
                         v st        j                  t*              rt        j                  t*              nd-t        j                  |      t        j                  |      t        j                  |      t        j                  |      d.	z  }d/d0|iz  }t        t        j                  |            dx}x}	x}x}x}x}x}}|j(                  }d1}	 ||	      }d}||k(  }|st        j                  d|fd2||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)3u9   P4) TASKCTL_BYPASS=1 + reason → PASS + evidence 기록.r   1z"emergency push for outage rollback)TASKCTL_BYPASSTASKCTL_BYPASS_REASONr2   )r4   	env_extrar   r6   r8   r9   r:   u"   bypass push 통과 못함. stdout=r>   r?   r@   N.tasksevidencer-      evidence 디렉토리 미생성C
>assert %(py4)s
{%(py4)s = %(py2)s
{%(py2)s = %(py0)s.is_dir
}()
}evidence_dirr;   r<   py4zbypass-*.jsonu   bypass-*.json 부재. 내용=
>assert %(py0)sr;   bypass_filesr   r	   bypassT)is)zI%(py6)s
{%(py6)s = %(py2)s
{%(py2)s = %(py0)s.get
}(%(py4)s)
} is %(py9)sdatar;   r<   r   py6rf   rg   rh   	timestampzEassert %(py6)s
{%(py6)s = %(py2)s
{%(py2)s = %(py0)s.get
}(%(py4)s)
}r;   r<   r   r   actorUSER)z%(py6)s
{%(py6)s = %(py2)s
{%(py2)s = %(py0)s.get
}(%(py4)s)
} == %(py16)s
{%(py16)s = %(py12)s
{%(py12)s = %(py10)s
{%(py10)s = %(py8)s.environ
}.get
}(%(py14)s)
}os)	r;   r<   r   r   py8rs   rt   ru   py16zassert %(py18)spy18reasonzI%(py6)s
{%(py6)s = %(py2)s
{%(py2)s = %(py0)s.get
}(%(py4)s)
} == %(py9)s)r)   rI   rJ   rK   rL   rM   rN   rO   rP   rQ   rR   rS   rT   is_dirlistglobiterdirjsonloads	read_textgetr   environ)rU   rV   rW   rY   r0   envr9   rZ   r[   r\   r]   r^   r   @py_format5r   @py_format1r   @py_assert5rl   @py_assert7rm   rn   @py_format7r|   r}   @py_assert13@py_assert15@py_format17@py_format19s                                r   2test_bypass_with_reason_passes_and_writes_evidencezBTestPrePushPass.test_bypass_with_reason_passes_and_writes_evidence   s    '&)!%I
 (JjlcR   	
A 	
 A% 	
 	
 A 	
 	
	6	
 	
   	
 	
 		  	
 	
 		 ! 	
 	
 		 %& 	
 	
  10A&--IZ[	
 	
 	
 	
 	
  (*Z7+E""G"$G$GG&GGGGGGG|GGG|GGG"GGG$GGGGGGL--o>?[[<T,BVBVBX=Y<Z[[[[[[[|[[[|[[[[[zz,q/33W3EFxx))x!)T)!T))))!T))))))t)))t)))x))))))!)))T)))))))xx$$x$$$$$$$$t$$$t$$$x$$$$$$$$$$$$$xx::x :BJJ:JNN:6:N6$:: $::::: $:::::::t:::t:::x:::::: ::::::B:::B:::J:::N:::6:::$:::::::::xxIIx!I%II!%IIIII!%IIIIIIItIIItIIIxIIIIII!III%IIIIIIIIIr   N)r   r   r   r   r   r$   r   r   r   r      s    
0Jr   r   c                      e Zd Zd Zy)TestPrePushFallbackc                   d} |||        ||d|        ||ddg       t        |dd       |dz  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}x}	}
 ||dt                     }|j                  }d}||k(  }	|	st        j                  d|	fd||f      dt	        j
                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      dz  }t        j                  d|j                  d|j                        dz   d|iz  }t        t        j                  |            dx}x}	}|dz  dz  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#                  d            }|st        j                  d t!        |j%                                      d!z   d"d#t	        j
                         v st        j                  |      rt        j                  |      nd#iz  }t        t        j                  |            t'        j(                  |d   j+                  d$%            }|j,                  }d&}	 ||	      }
d'}|
|k(  }|st        j                  d|fd(|
|f      d)t	        j
                         v st        j                  |      rt        j                  |      nd)t        j                  |      t        j                  |	      t        j                  |
      t        j                  |      d*z  }t        j                  d+|       d,z   d-|iz  }t        t        j                  |            dx}x}	x}
x}}|j,                  }d.}	 ||	      }
|
st        j                  d/|       d0z   d)t	        j
                         v st        j                  |      rt        j                  |      nd)t        j                  |      t        j                  |	      t        j                  |
      d1z  }t        t        j                  |            dx}x}	}
|j,                  }d2}	 ||	      }
d3}|
|k(  }|st        j                  d|fd(|
|f      d)t	        j
                         v st        j                  |      rt        j                  |      nd)t        j                  |      t        j                  |	      t        j                  |
      t        j                  |      d*z  }t        j                  d4|       d,z   d-|iz  }t        t        j                  |            dx}x}	x}
x}}|j,                  }d5}	 ||	      }
d3}|
|k(  }|st        j                  d|fd(|
|f      d)t	        j
                         v st        j                  |      rt        j                  |      nd)t        j                  |      t        j                  |	      t        j                  |
      t        j                  |      d*z  }t        j                  d6|       d,z   d-|iz  }t        t        j                  |            dx}x}	x}
x}}y)7u   taskctl verify 가 사용 불가 → fallback evidence 기록 + PASS.

        - 파일: .tasks/evidence/<task-id>/verify-fallback-*.json
        - 필드: taskctl_verify_status="fallback", reason, lock_check="PASS", scope_check="PASS"
        r   r-   r.   r   r   ztests/git_hooks/in_scope.txtr   scriptsztaskctl_verify.pyu*   테스트 전제: taskctl_verify.py 부재zG
>assert not %(py4)s
{%(py4)s = %(py2)s
{%(py2)s = %(py0)s.exists
}()
}verify_scriptr   Nr2   r3   r   r6   r8   r9   r:   u   fallback 통과 못함. stdout=r>   r?   r@   r   r   r   r   r   zverify-fallback-*.jsonu&   verify-fallback-*.json 부재. 내용=r   r;   fallback_filesr   r	   taskctl_verify_statusfallbackr   r   r   z#taskctl_verify_status != fallback: z
>assert %(py11)srh   r   u   reason 누락: zG
>assert %(py6)s
{%(py6)s = %(py2)s
{%(py2)s = %(py0)s.get
}(%(py4)s)
}r   
lock_checkPASSzlock_check != PASS: scope_checkzscope_check != PASS: )r   existsrJ   rP   rL   rM   rN   rO   rS   rT   r)   rI   rK   rQ   rR   r   r   r   r   r   r   r   r   )rU   rV   rW   rX   r   rY   r0   r   rZ   r\   r   r]   r9   r[   r^   r   r   r   r   r   rl   r   rm   rn   r   s                            r   2test_taskctl_verify_missing_uses_fallback_evidencezFTestPrePushFallback.test_taskctl_verify_missing_uses_fallback_evidence   s    '&)(K?-.	

 	X=vF !9,/BB ''W')W))W)WW+WWWWWWW=WWW=WWW'WWW)WWWWWW(JjlC   	
A 	
 A% 	
 	
 A 	
 	
	6	
 	
   	
 	
 		  	
 	
 		 ! 	
 	
 		 %& 	
 	
  .fmm->hv}}FWX	
 	
 	
 	
 	
  (*Z7+E""G"$G$GG&GGGGGGG|GGG|GGG"GGG$GGGGGGl//0HIJ 	
  5T,:N:N:P5Q4RS	
 	
	6	
 	
   	
 	
 		  	
 	
 	
 	
 	
 zz.+55w5GHxx 	
/ 	
x/0 	
J 	
0J> 	
 	
0J 	
 	
	6	
 	
   	
 	
 		  	
 	
 		  	
 	
 		 0 	
 	
 		 1 	
 	
 		 5? 	
 	
  2$8	
 	
 	
 	
 	
 	
 xx;;x!;!;;_TF#;;;;;;;t;;;t;;;x;;;;;;!;;;;;;xxNNx%NN%/NNN%NNNNNNtNNNtNNNxNNNNNN%NNNNNN3Gv1NNNNNNNNxxPPx&P&P&&0PPP&&PPPPPPtPPPtPPPxPPPPPP&PPP&PPP4I$2PPPPPPPPr   N)r   r   r   r   r$   r   r   r   r      s    .Qr   r   )x)r   r   r   r   r   r   returnNone)r   )r%   r   r   r   )__doc__
__future__r   builtinsrL   _pytest.assertion.rewrite	assertionrewriterJ   r   r   r   pathlibr   r   r)   r+   r   r   r$   r   r   <module>r      sP   & #    	  NO: O:j1J 1Jn/Q /Qr   