
    i8                     B   d 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ZdZdZ	ej                  j                  e	dd      Zej                  j                  de	       ded	ed
edefdZd	edededej"                  f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/dev7/test_scope_guard.py

dev7 scope-guard pytest 시나리오 5종 (task-2364)

시나리오:
1. 정상 케이스: snapshot 작성 → 정상 diff → exit 0
2. forbidden 위반: codegraph cron 케이스 → exit 1 + scope-violation.json
3. legacy 호환: snapshot 없음 + .allow-no-scope.log 마커 → exit 0
4. snapshot 없음, 마커도 없음: exit 1
5. 시스템 자동 파일 무시: diff에 memory/heartbeats/foo.json만 → exit 0

함수 단위 테스트:
- _parse_allowed_resources: yaml 파싱, inline list, 못 찾는 경우
- _save_capability_snapshot: sha256, auto forbidden 보강
    N)Pathz/home/jay/workspacez-/home/jay/workspace/.worktrees/task-2364-dev7scriptsztask-scope-guard.shcaps_dirtask_idsnapshotreturnc                     | j                  dd       | | dz  }|j                  t        j                  |      d       |S )u$   테스트용 snapshot 파일 작성.Tparentsexist_ok.jsonzutf-8)encoding)mkdir
write_textjsondumps)r   r   r   	snap_files       L/home/jay/workspace/.worktrees/task-2374-dev7/tests/dev7/test_scope_guard.pywrite_snapshotr   &   sE    NN4$N/gYe,,IH-@    diff_contentenv_workspacec                    ddl }|j                  ddd      5 }|j                  |       |j                  }ddd       	 t        j
                  j                         }||d<   t        j                  dt        | gd	d	|
      }|t	        j                  |       S # 1 sw Y   exY w# t	        j                         w xY w)u,   task-scope-guard.sh를 subprocess로 호출.r   Nwz.txtF)modesuffixdelete	WORKSPACEbashT)capture_outputtextenv)tempfileNamedTemporaryFilewritenameosenvironcopy
subprocessrunSCOPE_GUARD_SCRIPTunlink)r   r   r   r#   f	diff_filer"   results           r   run_scope_guardr1   .   s    		$	$#fU	$	K q	FF	jjoo(K')<	
 
		)  			)s   B AB' B$'B>c           	      (   d}| dz  dz  }| dz  dz  }|j                  dd       |ddd	d
gdgg dddd}t        |||       d}t        ||t        |             }	 |j                  dk(  s.J d|j                   d|j
                   d|j                          d|j
                  v sJ d|j
                          	 || dz  }|j                         r|j                          yy# || dz  }|j                         r|j                          w w xY w)uF   정상 케이스: snapshot 있고 diff 파일이 scope 내 → exit 0.ztest-sg-scenario1memorycapabilitieseventsTr
   2026-05-02T17:00:00deadbeefscripts/finish-task.sh%memory/plans/bot-capability-system/**memory/events/*.cron-*manual0   pathsforbidden_pathscommandsmerge_policy	ttl_hoursr   captured_atsource_sha256allowed_resourceszBscripts/finish-task.sh
memory/plans/bot-capability-system/plan.md
r      exit 0 기대, got 
stdout=
stderr=PASS   PASS 메시지 기대
stdout=r   N	r   r   r1   str
returncodestdoutstderrexistsr-   tmp_pathr   r   
events_dirr   r   r0   r   s           r   test_scenario1_passrU   F   sd   !G("^3HH$x/JTD1 ,#.0WX 89$
	H 8Wh/YLWlCMBF  A%  	A)<V=N=N<OyY_YfYfXggpqwq~q~p  (A  	A%&X*H(XX& '%00	  '%00	 s   AC& &+Dc           	      x   d}| dz  dz  }| dz  dz  }|j                  dd       |ddd	gd
gg dddd}t        |||       d}t        ||t        |             }|| dz  }	 |j                  dk(  s.J d|j                   d|j
                   d|j                          |j                         s
J d|        t        |      5 }t        j                  |      }	ddd       	d   |k(  sJ t        |	d         dkD  sJ t        d |	d   D              sJ 	 |j                         r|j                          || dz  }
|
j                         r|
j                          yy# 1 sw Y   xY w# |j                         r|j                          || dz  }
|
j                         r|
j                          w w xY w)uL   forbidden 위반: scope 밖 파일 수정 → exit 1 + scope-violation.json.ztest-sg-scenario2r3   r4   r5   Tr
   r6   r7   r8   r:   r;   r<   r=   rC   z!memory/events/cron-CC712188.json
z.scope-violation.json      exit 1 기대, got rH   rI   u   scope-violation.json 없음: Nr   
violationsr   c              3   *   K   | ]  }d |d   v   yw)zcron-CC712188.jsonpathN ).0vs     r   	<genexpr>z5test_scenario2_forbidden_violation.<locals>.<genexpr>   s     R'1V94Rs   r   )r   r   r1   rM   rN   rO   rP   rQ   openr   loadlenanyr-   )rS   r   r   rT   r   r   r0   violation_filer.   vdatar   s              r   "test_scenario2_forbidden_violationrf   l   s   !G("^3HH$x/JTD1 ,#./ 89$
	H 8Wh/ 8LWlCMBFWI-B"CCN  A%  	A)<V=N=N<OyY_YfYfXggpqwq~q~p  (A  	A%$$&X*GGW(XX&.! 	!QIIaLE	!Y7***5&'!+++ReL>QRRRR  "!!#'%00	 	! 	!   "!!#'%00	 s&   #A"E. E"<E. "E+'E. .AF9c                    d}| dz  dz  }|j                  dd       | dz  dz  j                  dd       || dz  }|j                  t        j                  |dd	             d
}t	        ||t        |             }	 |j                  dk(  s.J d|j                   d|j                   d|j                          d|j                  j                         v s0d|j                  j                         v sJ d|j                          |j                         r|j                          yy# |j                         r|j                          w w xY w)uI   legacy 호환: snapshot 없음 + .allow-no-scope.log → exit 0 + 경고.ztest-sg-scenario3r3   r5   Tr
   r4   z.allow-no-scope.logtest)r   reasonscripts/finish-task.sh
r   rG   rH   rI   legacyzallow-no-scopeu&   legacy 경고 메시지 없음
stderr=N)r   r   r   r   r1   rM   rN   rO   rP   lowerrQ   r-   )rS   r   rT   marker_filer   r0   s         r   $test_scenario3_legacy_allow_no_scopern      sd   !GH$x/JTD1>)000M '*=>>K4::'V&LMN-LWlCMBF!  A%  	A)<V=N=N<OyY_YfYfXggpqwq~q~p  (A  	A%6==..004DH[H[H]4] 	F5fmm_E	F]    ;   s   ?B	D* *#Ec                    d}| dz  dz  j                  dd       | dz  dz  j                  dd       d}t        ||t        |             }|j                  dk(  s.J d	|j                   d
|j                   d|j
                          d|j
                  j                         v s1d|j
                  j                         v sJ d|j
                          yy)uA   snapshot 없음 + 마커도 없음 → exit 1 + 에러 메시지.ztest-sg-scenario4r3   r4   Tr
   r5   rj   rW   rX   rH   rI   r   dispatchu   에러 메시지 없음
stderr=N)r   r1   rM   rN   rO   rP   rl   )rS   r   r   r0   s       r   $test_scenario4_no_snapshot_no_markerrq      s    !G>)000M8#**4$*G-LWlCMBF!|%89J9J8K9U[UbUbTcclmsmzmzl{#||!,,..*@S@S@U2U ;
*6==/:;U2U.r   c           	         d}| dz  dz  }| dz  dz  }|j                  dd       |ddd	gg g d
ddd}t        |||       d}t        ||t        |             }|| dz  }	 |j                  dk(  s.J d|j                   d|j
                   d|j                          d|j
                  v sJ d|j
                          	 |j                         r|j                          yy# |j                         r|j                          w w xY w)uB   시스템 자동 파일(memory/heartbeats/...)만 diff → exit 0.ztest-sg-scenario5r3   r4   r5   Tr
   r6   r7   r8   r;   r<   r=   rC   zememory/heartbeats/foo.json
memory/daily/2026-05-02.md
bot-activity.json
token-ledger.json
.heartbeat
r   r   rG   rH   rI   rJ   rK   NrL   rR   s           r   #test_scenario5_system_files_ignoredrs      sO   !G("^3HH$x/JTD1 ,#./!$
	H 8Wh/	  WlCMBFgYe,,I  A%  	A)<V=N=N<OyY_YfYfXggpqwq~q~p  (A  	A%&X*H(XX& 9 s   "AC$ $#Dc                      ddl m}  d} | |      }|J d       d|d   v sJ d|d   v sJ d	|d
   v sJ |d   dk(  sJ |d   dk(  sJ y)u2   fenced yaml 블록에서 allowed_resources 파싱.r   _parse_allowed_resourcesa  
# task
## allowed_resources
```yaml
allowed_resources:
  paths:
    - "scripts/finish-task.sh"
    - "memory/plans/bot-capability-system/**"
  forbidden_paths:
    - "memory/events/*.cron-*"
  commands:
    - "pytest"
  merge_policy: "manual"
  ttl_hours: 48
```
Nu"   파싱 결과가 None이면 안됨r8   r>   r9   r:   r?   rA   r;   rB   r<   rp   rv   rv   r!   rs      r   'test_parse_allowed_resources_yaml_blockrz      s    1D  	!&A=>>>=#qz1112aj@@@#q):';;;;^((([>Rr   c                  b    ddl m}  d} | |      }|J d|d   v sJ d|d   v sJ d|d	   v sJ y)
u"   인라인 리스트 형식 파싱.r   ru   z
```yaml
allowed_resources:
  paths: [foo.py, bar.py]
  forbidden_paths: [secrets/**]
  merge_policy: manual
  ttl_hours: 24
```
Nzfoo.pyr>   zbar.py
secrets/**r?   rw   rx   s      r   (test_parse_allowed_resources_inline_listr}     sZ    1D 	!&A==qz!!!qz!!!1.////r   c                  <    ddl m}  d} | |      }|
J d|        y)u,   allowed_resources 없는 경우 None 반환.r   ru   u1   # 단순 task 설명
어떤 작업을 합니다.
N   None 기대, got rw   rx   s      r   &test_parse_allowed_resources_not_foundr   (  s+    1@D &A9-)!--9r   c                  <    ddl m}  d} | |      }|
J d|        y)u>   yaml 블록이 있어도 allowed_resources 키 없으면 None.r   ru   z#
```yaml
other_key:
  foo: bar
```
Nr   rw   rx   s      r   -test_parse_allowed_resources_yaml_without_keyr   1  s/    1D 	!&A9-)!--9r   c                  ,   ddl m}  d}dgg g ddd}d} | |||      }	 |j                         s
J d	|        t        |      5 }t	        j
                  |      }d
d
d
       d   |k(  sJ |d   sJ d       dd
l}|j                  |j                               j                         }|d   |k(  sJ d|d    d|        	 |j                         r|j                          y
y
# 1 sw Y   xY w# |j                         r|j                          w w xY w)u'   기본 snapshot 저장 + sha256 확인.r   _save_capability_snapshotztask-test-snap-basicr8   r;      r=   zhello world snapshot testu   snapshot 파일 없음: Nr   rE   u   sha256 비어있음u   sha256 불일치: z != )rp   r   rQ   r`   r   ra   hashlibsha256encode	hexdigestr-   )	r   r   arsource_text	snap_pathr.   datar   expecteds	            r   #test_save_capability_snapshot_basicr   C  s<   2$G*+ 
B .K)'2{CI!I%=i[#II!)_ 	 99Q<D	 I')))O$;&;;$>>+"4"4"67AACO$0l4FtOG\F]]abjak2ll0 	  	   s$   %C0 C$A'C0 $C-)C0 0#Dc                     ddl m}  d}dgdgg ddd} | ||d	      }	 t        |      5 }t        j                  |      }d
d
d
       d   d   }d|v s
J d|        d|v s
J d|        	 |j                         r|j                          y
y
# 1 sw Y   PxY w# |j                         r|j                          w w xY w)u,   memory/capabilities/** 자동 보강 확인.r   r   ztask-test-snap-forbiddenr8   r|   r;   r   r=   test sourceNrF   r?   memory/capabilities/**u-   memory/capabilities/** 자동 보강 없음: u&   기존 forbidden_paths 유지 안됨: )rp   r   r`   r   ra   rQ   r-   )r   r   r   r   r.   r   	forbiddens          r   9test_save_capability_snapshot_auto_forbidden_capabilitiesr   a  s    2(G*+(> 
B *'2}EI
)_ 	 99Q<D	 ,-.?@	'94 	H;I;G	H4y( 	A4YK@	A(  	  	   s!   B B,B BB #B=c                     ddl m}  d}dgdgg ddd} | ||d	      }	 t        |      5 }t        j                  |      }d
d
d
       d   d   }|j                  d      }|dk(  s
J d|        	 |j                         r|j                          y
y
# 1 sw Y   TxY w# |j                         r|j                          w w xY w)uA   이미 memory/capabilities/**가 있으면 중복 추가 안 함.r   r   ztask-test-snap-nodupr8   r   r;   r   r=   r   NrF   r?   rW   u   중복 추가됨: )rp   r   r`   r   ra   countrQ   r-   )r   r   r   r   r.   r   r   r   s           r   4test_save_capability_snapshot_no_duplicate_forbiddenr   |  s    2$G*+45 
B *'2}EI)_ 	 99Q<D	 ,-.?@	 89z;/	{;;z 	  	   s!   B B0B BB #C)__doc__r   r'   r*   syspathlibr   pytestWORKSPACE_ROOTWORKTREE_ROOTr[   joinr,   insertrM   dictr   CompletedProcessr1   rU   rf   rn   rq   rs   rz   r}   r   r   r   r   r   r\   r   r   <module>r      s   "  	  
   '?WW\\-<QR  = !T C 4 D S  C JLgLg 0L(^!:;&%X :0(..$<6r   