
    Yi1                       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ZddlmZmZmZ ddlmZ ddlZ ed      Zedz  dz  Zd	Z ed
      dz  dz  ZefddZddZddZddZ	 d	 	 	 ddZddZdddZd Zd Zd Z y) u  tests/taskctl/test_admin_override.py
Admin Override audit log + cap 검증 (task-2467)

벨레스(개발6팀 테스터) 작성. 스바로그의 구현 완료 전 선작성(TDD).

케이스:
    1. test_admin_override_records_audit_log — chairman email로 --admin → audit log 1줄
    2. test_admin_override_non_chairman_blocked — 다른 사용자 --admin → FAIL
    3. test_admin_override_hard_cap — 90일 내 5건 채운 후 6번째 → HARD_CAP_EXCEEDED
    )annotationsN)datetime	timedeltatimezone)Pathz-/home/jay/workspace/.worktrees/task-2467-dev6scriptsz
taskctl.pyzjonghyuk.jeon@gmail.commemoryorchestration-auditzadmin-override.jsonlc                x   i t         j                  }t        |       |d<   ||d<   ||d<   t        | dz        |d<   | dz  dz  j                  dd	       | dz  d
z  j                  dd	       | dz  dz  j                  dd	       | dz  dz  j                  dd	       | dz  }|j	                  d| d       |S )u3   격리된 workspace + git config user.email 설정.WORKSPACE_ROOTGIT_AUTHOR_EMAILGIT_COMMITTER_EMAILz
.gitconfigGIT_CONFIG_GLOBAL.tasksstateTparentsexist_okevidencer	   eventsr
   z[user]
	email = z
	name = Test Chairman
)osenvironstrmkdir
write_text)tmp_pathchairman_emailenv	gitconfigs       8/home/jay/workspace/tests/taskctl/test_admin_override.py_isolated_workspacer!      s    
RZZ.CMC,C!/C"8l#:;C7"))$)F:%,,TD,I8#**4$*G0077t7T <'I
^,,FG J    c                Z    t        j                  dt        t              g| z   dd|d      S )Npython3T   )capture_outputtextr   timeout)
subprocessrunr   TASKCTL)argsr   s     r    _runr-   5   s-    >>	CL!D($C r"   c                   | 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                  |j                               S )Nr   r   .jsonu   state 파일 없음: zC
>assert %(py4)s
{%(py4)s = %(py2)s
{%(py2)s = %(py0)s.exists
}()
}p)py0py2py4)exists
@pytest_ar_format_assertmsg@py_builtinslocals_should_repr_global_name	_safereprAssertionError_format_explanationjsonloads	read_text)r   task_idr0   @py_assert1@py_assert3@py_format5s         r    _staterD   <   s    8g%7)5(99A8828:2:22.qc2222222122212228222:222222::akkm$$r"   c                   | dz  dz  | dz  }t        j                  |j                               }||d<   |j                  dd       |j	                         D ci c]  \  }}|dk7  s|| }}}t        j
                  |ddd	
      }t        j                  |j                  d            j                         }	|	|d<   |j                  t        j
                  |dd             yc c}}w )u<   테스트 전용: state 강제 설정 + 체크섬 재계산.r   r   r/   current_state	_checksumNFT,:ensure_ascii	sort_keys
separatorsutf-8   rL   indent)r=   r>   r?   popitemsdumpshashlibsha256encode	hexdigestr   )
r   r@   target_stater0   r   kvpayloadcanonchecksums
             r    _force_state_with_checksumr`   B   s    8g%7)5(99AJJq{{}%E)E/	IIk4  %B1k1Aq!tBGBJJwUdzZE~~ell734>>@H!E+LLEa@A	 Cs   C**C*c           
        d|gd|gd|gd|gd|dt        |      gfD ]  }t        ||      j                  dk7  s y t        | |d	       | d
z  dz  | dz  }t	        j
                  |j                               }d|d<   |j                  dd       |j                         D ci c]  \  }}|dk7  s|| }	}}t	        j                  |	ddd      }
t        j                  |
j                  d            j                         |d<   |j                  t	        j                  |dd             t        | |      d   d	k(  S c c}}w )u*   HUMAN_APPROVED 상태까지 강제 진입.initdispatchackr*   zpr-openz--prr   FHUMAN_APPROVEDr   r   r/   Thuman_approvedrG   NrH   rK   rO   rP   rQ   rF   )r   r-   
returncoder`   r=   r>   r?   rS   rT   rU   rV   rW   rX   rY   r   rD   )r   r   r@   	pr_numbercmdr0   r   r[   r\   r]   r^   s              r    _setup_to_human_approvedrj   O   sK    !J#8 5'"2GVS^<>  S>$$)	 x2BC8g%7)5(99AJJq{{}%E"E
	IIk4  %B1k1Aq!tBGBJJwUdzZE W(=>HHJE+LLEa@A(G$_59III Cs   %E3Ec                h   | t         z  }|j                         sg S |j                         j                         D cg c]#  }|j	                         s|j	                         % }}g }|D ]'  }	 |j                  t        j                  |             ) |S c c}w # t        j                  $ r Y Fw xY w)u   audit log JSONL 읽기.)	AUDIT_LOG_RELATIVEr4   r?   
splitlinesstripappendr=   r>   JSONDecodeError)r   	audit_loglnlinesresults        r    _read_audit_logru   g   s    --I	"+"5"5"7"B"B"DSB
RXXZSESF 	MM$**R.)
 M T
 ## 		s   BB.$BB10B1c                   | t         z  }|j                  j                  dd       t        j                  t
        j                        }t        |dd      5 }t        |      D ]p  }|t        ||z  dz         z
  }|j                  d      d	|d
d|z   dd| d|ddg|dz   |dz   d	}|j                  t        j                  |d      dz          r 	 ddd       y# 1 sw Y   yxY w)u9   audit log에 count건 미리 기록 (rolling window 내).Tr   arO   )encoding   )daysz%Y-%m-%dT%H:%M:%SZztask-prepop-03di  chairmanzprepopulated test record #abc04dtest)	tsr@   rh   actorreasonhead_shabypassed_checkssoft_count_this_monthhard_count_this_quarterF)rL   
N)rl   parentr   r   nowr   utcopenranger   strftimewriter=   rU   )	r   countdays_ago_maxrq   r   fir   records	            r    _prepopulate_audit_logr   v   s    --I4$7
,,x||
$C	iw	/ C1u 	CAya,&6&:;;Bkk"67)!C1!AX#6qc:!!CM$*8)*Q+,q5
F GGDJJvE:TAB	CC C Cs   A?C!!C*c           
     :
   t        | t              }d}d}t        | |||      }|st        j                  d       t        d|ddg|      }|j                  dk(  rt        |       }t        |      dk(  r&t        j                  d	t        | |      d
    d       |d   }dD ]  }||v }	|	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| dt%        |j'                                      dz   d|
iz  }t)        t        j*                  |            d}	 |j,                  }	d} |	|      }||k(  }|s*t        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                  |      rt        j                   |      nddz  }t        j"                  d|j-                  d             dz   d|iz  }t)        t        j*                  |            dx}	x}x}}|j,                  }	d} |	|      }d t        d!h}||v }|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$|j-                  d             d%z   d&|iz  }t)        t        j*                  |            dx}	x}x}x}}t        | |      }|d
   }h 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t        j                  d.|j.                  dd/         y)0u   chairman email로 taskctl merge --admin → audit log 1줄 + ADMIN_OVERRIDE_USED.

    신규 구현 필요. MVP에서는 --admin 플래그 미구현 시 xfail.
    r   ztask-adminoverride-01i  #   HUMAN_APPROVED 상태 진입 실패merge--admin	--dry-runr   u~   --admin 플래그 인식되나 audit log 미구현 (스바로그 대기): merge 성공했지만 audit log 비어있음 (state=rF   ))r   r@   r   r   r   )in)z%(py0)s in %(py2)sfieldlast)r1   r2   u   audit log에 'u
   ' 누락: z
>assert %(py4)sr3   Nr@   ==)zI%(py6)s
{%(py6)s = %(py2)s
{%(py2)s = %(py0)s.get
}(%(py4)s)
} == %(py8)s)r1   r2   r3   py6py8u   audit log task_id 불일치: z
>assert %(py10)spy10r   r|   z"chairman (jonghyuk.jeon@gmail.com))zI%(py6)s
{%(py6)s = %(py2)s
{%(py2)s = %(py0)s.get
}(%(py4)s)
} in %(py9)s)r1   r2   r3   r   py9u   audit log actor 이상: z
>assert %(py11)spy11>   DONEMERGEDADMIN_OVERRIDE_USED)z%(py0)s in %(py3)scurrent)r1   py3u&   admin override 후 예상 외 상태: z
>assert %(py5)spy5uQ   --admin 플래그 미구현 또는 chairman 검증 실패 (스바로그 대기):    )r!   CHAIRMAN_EMAILrj   pytestskipr-   rg   ru   lenxfailrD   r5   _call_reprcomparer7   r8   r9   r:   r6   listkeysr;   r<   getstderr)r   r   r@   rh   okprocentriesr   r   rA   @py_format3rC   rB   @py_assert5@py_assert7@py_format9@py_format11@py_assert8@py_format10@py_format12r   r   @py_assert2@py_format4@py_format6s                            r    %test_admin_override_records_audit_logr      sy   
 h~
FC%GI	!(C)	DB9: '9k:C@D!!(+w<1LLGGMhX_G`apGqFrrsu
 r{ U 	XED=WWW5DWWWWWW5WWW5WWWWWWDWWWDWWWWN5'DDUCV"WWWWWWW	X xx 	
	 	
x	" 	
"g- 	
 	
 	
"g 	
 	
	6	
 	
   	
 	
 		  	
 	
 		  	
 	
 		 " 	
 	
 		 # 	
 	
	6	
 	
  '. 	
 	
 		 '. 	
 	
  ,DHHY,?+@A	
 	
 	
 	
 	
 	
 xx 	
 	
x  	
ZAe$f 	
 $ff 	
 	
 $f 	
 	
	6	
 	
   	
 	
 		  	
 	
 		  	
 	
 		   	
 	
 		 ! 	
 	
 		 %g 	
 	
  'txx'8&9:	
 	
 	
 	
 	
 	

 x)(C 	
wCC 	
 	
wC 	
 	
	6	
 	
   	
 	
 		  	
 	
 		 D 	
 	
  5WI>	
 	
 	
 	
 	
 	_{{4C !#	
r"   c                   d}t        | |      }d}d}t        | |||      }|st        j                  d       t	        d|ddg|      }|j
                  d	k(  r0t        j                  d
|j                   d|j                          y|j                  |j                  z   }d|j                         v xs: d|j                         v xs& d|j                         v xs d|j                         v }d|j                         v xs& d|j                         v xs d|j                         v }	|	r|st        j                  d       |j
                  }
d	}|
|k7  }|st        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  }t#        t        j$                  |            dx}
x}}y)uy   다른 사용자가 --admin 시도 → FAIL ("chairman only").

    git config user.email을 다른 email로 설정.
    znon-chairman@example.comr   ztask-adminoverride-02i  r   r   r   r   r   u5   non-chairman이 --admin 성공: returncode=0
stdout: 	
stderr: r|   unauthorized
permissiononlyunknownunrecognized1   --admin 플래그 미구현 (스바로그 대기)!=z2%(py2)s
{%(py2)s = %(py0)s.returncode
} != %(py5)sr   r1   r2   r   assert %(py7)spy7N)r!   rj   r   r   r-   rg   failstdoutr   lowerr   r5   r   r7   r8   r9   r:   r;   r<   )r   non_chairman_emailr   r@   rh   r   r   combinedis_chairman_blockedis_not_implementedrA   @py_assert4rB   r   @py_format8s                  r    (test_admin_override_non_chairman_blockedr      s   
 4
h7I
JC%GI	!(C)	DB9:'9k:C@D!{{m:dkk]<	
 ;;,(..** *!11*x~~//* ))	 	 )) 2HNN,,2!11 	 &9LLLM #!#!####!######t###t######!#######r"   c                	   t        | t              }d}d}t        | |||      }|st        j                  d       t        | dd       t        |       }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#        d|ddg|      }|j$                  dk(  rkt        |       }t        |      dkD  r<t        j&                  dt        |       d|j(                   d|j*                          yt        j,                  d       y|j*                  |j(                  z   }d|j/                         v xs@ d|j1                         v xs, d|j1                         v xs d|j1                         v xs d|v }d|j1                         v xs& d|j1                         v xs d |j1                         v }|r|st        j,                  d!       |s|st        j,                  d"|dd#         |j$                  }d}||k7  }|st        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  }t        t        j                   |            dx}x}}t        |       }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}}y).u   audit log에 90일 내 5건 미리 채운 후 6번째 시도 → FAIL (HARD_CAP_EXCEEDED).

    명세 §6.2: hard_count >= 5 → "HARD_CAP_EXCEEDED".
    r   ztask-adminoverride-03i  r      Y   )r   r   r   )z0%(py3)s
{%(py3)s = %(py0)s(%(py1)s)
} == %(py6)sr   entries_before)r1   py1r   r   u   prepopulate 실패: u   건z
>assert %(py8)sr   Nr   r   r   r   u7   hard cap 초과에도 admin override 성공: audit log u   건
stdout: r   u/   hard cap 검사 미구현 (스바로그 대기)HARD_CAPcapexceededlimit5r   r   r   uE   hard cap 관련 메시지 없음 (chairman 검증 실패 가능성): i,  r   r   r   r   r   r   )<=)z0%(py3)s
{%(py3)s = %(py0)s(%(py1)s)
} <= %(py6)sentries_afteru2   hard cap 초과 후에도 audit log에 기록됨: )r!   r   rj   r   r   r   ru   r   r5   r   r7   r8   r9   r:   r6   r;   r<   r-   rg   r   r   r   r   upperr   )r   r   r@   rh   r   r   r   r   r   @py_format7r   r   r   r   is_cap_exceededr   rA   rB   r   r   s                       r    test_admin_override_hard_capr      s   
 h~
FC%GI	!(C)	DB9: 812>$X.N~T!T!#TTT!TTTTTT3TTT3TTTTTT~TTT~TTTTTT!TTT';C<O;PPS%TTTTTTTT '9k:C@D!'1}!KK /0 1;;-z$++@ LLJK;;, (..** ((X^^-- (..** h 	 )) 2HNN,,2!11 	 oLLLM'9LLWX`aebeXfWgh
 #!#!####!######t###t######!####### (1=! 	
Q 	
!Q& 	
 	
 	
!Q 	
 	
	6	
 	
   	
 	
 		  	
 	
	6	
 	
  ! 	
 	
 		 ! 	
 	
 		 " 	
 	
 		 &' 	
 	
  A]AS@TTWX	
 	
 	
 	
 	
 	
r"   )r   r   r   r   returndict)r,   z	list[str]r   r   r   zsubprocess.CompletedProcess)r   r   r@   r   r   r   )r   r   r@   r   rZ   r   r   None)ry   )
r   r   r   r   r@   r   rh   intr   bool)r   r   r   z
list[dict])r%   )r   r   r   r   r   r   r   r   )!__doc__
__future__r   builtinsr7   _pytest.assertion.rewrite	assertionrewriter5   rV   r=   r   r)   r   r   r   pathlibr   r   	WORKSPACEr+   r   rl   r!   r-   rD   r`   rj   ru   r   r   r   r    r"   r    <module>r      s   	 #     	  2 2  @A	
i
,
. +(^&;;>TT  ?M .%
B 01J),J59J0C44
v)$`A
r"   