
    Ri!                        d 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 ej                  j                  dd      Zej                  j                  dd      ZddlZ ee      j                  j                  Zej"                  j%                  d ee             ddlZed	z  Zej.                  j1                  d
e      ZeJ ej.                  j5                  e      Zej8                  J ej8                  j;                  e        G d d      Z G d d      Z G d d      Z  G d d      Z! G d d      Z" G d d      Z# G d d      Z$y)ul  
test_notify_completion.py

scripts/notify-completion.py 단위 테스트 (task-902.1 반영)

테스트 항목:
- argparse 파싱 정상 동작
- send_telegram_notification 기반 완료 통보 검증 (requests.post 방식)
- subprocess 호출 mock (실제 외부 전송 방지)
- 셸 인젝션 방지 (shell=False 패치)
- .done.notified 마커 생성 (O_EXCL)
    N)Path)	MagicMockpatchCOKACDIR_CHAT_ID
6937032012WORKSPACE_ROOTz/home/jay/workspacenotify-completion.pynotify_completionc                       e Zd ZdZd Zy)TestArgparseu   argparse 파싱 테스트c                    t        j                  t              5 }t        t        j
                  d      rt        j
                  j                  nd ddl}|j                  d      }|j                  d       |j                  dt               |j                  d	d       |j                  d
g       ddd       j                  j                  dk(  sJ y# 1 sw Y   %xY w)u   --help 플래그 정상 동작__wrapped__Nr   u   팀장 → 아누 완료 통보)descriptiontask_id	--chat-id)defaultz	--anu-keyz--help)pytestraises
SystemExithasattrr
   mainr   argparseArgumentParseradd_argument_TEST_CHAT_ID
parse_argsvaluecode)selfexc_infor   parsers       U/home/jay/workspace/.worktrees/task-2117-dev1/scripts/tests/test_notify_completion.pytest_help_flagzTestArgparse.test_help_flag+   s    ]]:& 	*(29:K:P:PR_2`""..fj,,9Z,[F	*]CT:xj)	* ~~""a'''	* 	*s   BCC!N)__name__
__module____qualname____doc__r#        r"   r   r   (   s
    #(r)   r   c                   L    e 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)"TestSendTelegramNotificationDirectuB   send_telegram_notification (requests.post 방식) 직접 테스트c                 p   t               }d|_        t        j                  dddi      5  t        d|      5 }t        j                  dd      }d	d	d	       d	d	d	       d
u sJ j                          |j                  }dt        |      v sJ dt        |      v sJ y	# 1 sw Y   RxY w# 1 sw Y   VxY w)u   200 응답 시 True 반환   
os.environANU_BOT_TOKEN
test-tokenrequests.postreturn_valuechat123   테스트 메시지NT)	r   status_coder   dictr
   send_telegram_notificationassert_called_once	call_argsstr)r   	mock_resp	mock_postresultcall_kwargss        r"   test_success_returns_truez<TestSendTelegramNotificationDirect.test_success_returns_true<   s    K	 #	ZZ&EF 	hY? h9*EEiQfgh	h ~~$$&))C,,,,$K(8888h h	h 	hs"   B,B B, B)	%B,,B5c                    t         j                  j                         D ci c]  \  }}|dk7  s|| }}}t        j                  d|d      5  t
        j                  dd      }ddd       du sJ yc c}}w # 1 sw Y   xY w)	u(   ANU_BOT_TOKEN 미설정 시 False 반환r/   r.   Tclearr4   	   메시지NF)osenvironitemsr   r7   r
   r8   )r   kvenvr>   s        r"   test_no_bot_token_returns_falsezBTestSendTelegramNotificationDirect.test_no_bot_token_returns_falseK   s~     "

 0 0 2K1a?6Jq!tKKZZc6 	Z&AA)[YF	Z L	Z 	Zs   A6A6A<<Bc                 
   t               }d|_        i |_        t        j                  dddi      5  t        d|      5  t
        j                  dd      }d	d	d	       d	d	d	       d
u sJ y	# 1 sw Y   xY w# 1 sw Y   xY w)u.   200이 아닌 응답(400 등) 시 False 반환  r.   r/   r0   r1   r2   r4   rD   NF)r   r6   headersr   r7   r
   r8   )r   r<   r>   s      r"   test_non_200_returns_falsez=TestSendTelegramNotificationDirect.test_non_200_returns_falseR   s    K	 #		ZZ&EF 	^Y? ^*EEiQ\]^	^ ^ ^	^ 	^s"   A9A-A9-A6	2A99Bc                    ddl }t               }d|_        ddi|_        t               }d|_        t	        j
                  ddd	i      5  t	        d
||g      5 }t	        d      5  t        j                  dd      }ddd       ddd       ddd       du sJ j                  dk(  sJ y# 1 sw Y   1xY w# 1 sw Y   5xY w# 1 sw Y   9xY w)u5   429 응답 시 Retry-After 헤더를 따라 재시도r   Ni  zRetry-After1r-   r.   r/   r0   r1   side_effect
time.sleepr4   rD   T   )	requestsr   r6   rN   r   r7   r
   r8   
call_count)r   real_requestsmock_429mock_200r=   r>   s         r"   !test_429_retries_with_retry_afterzDTestSendTelegramNotificationDirect.test_429_retries_with_retry_after]   s    (;")3/;"ZZ&EF 	bXx4HI bY<( b.II)U`aFbb	b
 ~~##q(((	b bb b	b 	bs<   CB7$B+;B7C+B40B77C 	<CCc                 @   ddl }t        j                  dddi      5  t        d|j                  d            5  t        d	      5  t        j                  d
d      }ddd       ddd       ddd       du sJ y# 1 sw Y    xY w# 1 sw Y   $xY w# 1 sw Y   (xY w)u2   네트워크 에러 시 재시도 후 False 반환r   Nr.   r/   r0   r1   zconnection errorrR   rT   r4   rD   F)rV   r   r7   RequestExceptionr
   r8   )r   rX   r>   s      r"   test_request_exception_retrieszATestSendTelegramNotificationDirect.test_request_exception_retriesp   s    (ZZ&EF 	bM4R4RSe4fg b<( b.II)U`aFbb	b b bb b	b 	bs:   BBA<B%B<BBB	BBc                 <   t               }d|_        t        j                  dddi      5  t        d|      5 }t        j                  dd       d	d	d	       d	d	d	       j                  }|d
   d
   }d|v sJ d|v sJ d|v sJ y	# 1 sw Y   8xY w# 1 sw Y   <xY w)u!   올바른 Telegram API URL 사용r-   r.   r/   
mytoken123r1   r2   r4   rD   Nr   zapi.telegram.orgsendMessage)r   r6   r   r7   r
   r8   r:   )r   r<   r=   r:   urls        r"   test_uses_correct_api_urlz<TestSendTelegramNotificationDirect.test_uses_correct_api_urlz   s    K	 #	ZZ&EF 	UY? U9!<<YTU	U ''	l1os"""!S(((###U U	U 	Us"   BBBB	BBc                 :   t               }d|_        t        j                  dddi      5  t        d|      5 }t        j                  dd       d	d	d	       d	d	d	       j                  d
   }|j                  d      dk(  sJ y	# 1 sw Y   7xY w# 1 sw Y   ;xY w)u   timeout=10 필수r-   r.   r/   r0   r1   r2   r4   rD   N   timeout
   r   r6   r   r7   r
   r8   r:   get)r   r<   r=   r?   s       r"   test_timeout_is_10_secondsz=TestSendTelegramNotificationDirect.test_timeout_is_10_seconds   s    K	 #	ZZ&EF 	UY? U9!<<YTU	U  ))!,y)R///	U U	U 	Us"   BBBB	
BBc                 ^   t               }d|_        t        j                  dddi      5  t        d|      5 }t        j                  dd       d	d	d	       d	d	d	       j                  d
   }|j                  di       }|j                  d      dk(  sJ y	# 1 sw Y   IxY w# 1 sw Y   MxY w)uJ   requests.post payload에 parse_mode: 'Markdown'이 포함되어야 한다.r-   r.   r/   r0   r1   r2   r4   r5   Nre   json
parse_modeMarkdownrh   )r   r<   r=   r?   payloads        r"   #test_parse_mode_markdown_in_payloadzFTestSendTelegramNotificationDirect.test_parse_mode_markdown_in_payload   s    K	 #	ZZ&EF 	_Y? _9!<<YH]^_	_  ))!,//&"-{{<(J666_ _	_ 	_s"   B#BB#B 	B##B,c                    t               }d|_        i |_        t               }d|_        t        j                  dddi      5  t        d||g      5 }t
        j                  dd	       d
d
d
       d
d
d
       j                  dk(  sJ |j                  d   d   }|j                  di       }|j                  d      dk(  sJ |j                  d   d   }|j                  di       }d|vsJ y
# 1 sw Y   xY w# 1 sw Y   xY w)u   Markdown 파싱 에러(400) 시 plain text fallback으로 2회 호출되어야 한다.
        첫 번째 호출에는 parse_mode가 있고 두 번째에는 없어야 한다.rM   r-   r.   r/   r0   r1   rR   r4   r5   NrU   r   re   rl   rm   rn   )
r   r6   rN   r   r7   r
   r8   rW   call_args_listri   )r   mock_400rZ   r=   first_call_kwargsfirst_payloadsecond_call_kwargssecond_payloads           r"   "test_markdown_parse_error_fallbackzETestSendTelegramNotificationDirect.test_markdown_parse_error_fallback   s    ;";"ZZ&EF 	_Xx4HI _Y!<<YH]^_	_ ##q(((%44Q7:)--fb9  .*<<<&55a8;+//;>111_ _	_ 	_s$   C;C/)C;/C8	4C;;Dc                    t               }d|_        i |_        t               }d|_        t        j                  dddi      5  t        d||g      5 }t
        j                  dd	      }d
d
d
       d
d
d
       j                  d   d   j                  di       }|j                  d      dk(  sJ du sJ y
# 1 sw Y   PxY w# 1 sw Y   TxY w)u   첫 번째 Markdown 전송이 400일 때 fallback 성공 시 True를 반환해야 한다.
        (parse_mode=Markdown → 400 → parse_mode 없는 재전송 → 200 → True)rM   r-   r.   r/   r0   r1   rR   r4   r5   Nr   re   rl   rm   rn   T)	r   r6   rN   r   r7   r
   r8   rr   ri   )r   rs   rZ   r=   r>   ru   s         r"   %test_fallback_returns_true_on_successzHTestSendTelegramNotificationDirect.test_fallback_returns_true_on_success   s     ;";"ZZ&EF 	hXx4HI hY*EEiQfgh	h
 "003A6::62F  .*<<<~~h h	h 	hs$   CB8)C8C	=CCN)r$   r%   r&   r'   r@   rK   rO   r[   r^   rc   rj   rp   rx   rz   r(   r)   r"   r+   r+   9   s7    L9	)&$
072,r)   r+   c                   l    e Zd ZdZddZ ed      d        Z ed      d        Z ed      d        Zy)	TestMainWithMocku/   main() 함수 테스트 (requests.post를 mock)Nc                 $    dddddfd}|S )u(   subprocess.run side_effect 공통 생성NFin_chainis_lastchain_idnext_task_idc                 D   t               }d|_        d|_        t        | t              rZdj                  d | D              }d|v r d|v rt        j                        |_        |S t        j                  ddi      |_        |S t        j                  ddi      |_        |S )	Nr     c              3   2   K   | ]  }t        |        y wNr;   .0cs     r"   	<genexpr>zUTestMainWithMock._make_subprocess_side_effect.<locals>.side_effect.<locals>.<genexpr>        "7a3q6"7   chain_manager.pycheckstatusok	r   
returncodestderr
isinstancelistjoinrl   dumpsstdout)cmdargskwargsr>   cmd_strchain_results        r"   rS   zBTestMainWithMock._make_subprocess_side_effect.<locals>.side_effect   s    [F !FFM#t$(("73"77%0W5G$(JJ|$<FM
 M %)JJ$/?$@FM M !%

Hd+; <Mr)   r(   )r   r   rS   s    ` r"   _make_subprocess_side_effectz-TestMainWithMock._make_subprocess_side_effect   s%    (-%TcghL	 r)   subprocess.runc           	      \   | j                         |_        |dz  dz  j                  dd       t               }d|_        t        dddg      5  t        j                  d	d
dd      5  t        j                  t        dt        |            5  t        d|      5 }t        j                          ddd       ddd       ddd       ddd       j                  dk\  sJ |j                  }t        |d         dkD  r|d   j                  d      xs |d   d   n|d   j                  d      }||d   r|d   j                  d      }|r|j                  dd      nd}d|v s
J d|        d|v s
J d|        y# 1 sw Y   xY w# 1 sw Y   xY w# 1 sw Y   xY w# 1 sw Y   xY w)uM   main()이 send_telegram_notification(requests.post)를 호출하는지 확인memoryeventsTparentsexist_okr-   sys.argvr	   
task-999.1r.   r0   test-anu-keyr/   COKACDIR_KEY_ANUr   r1   r2   Nre   r   rl   textr   u    메시지에 task_id가 없음:    완료u!   메시지에 '완료'가 없음: )r   rS   mkdirr   r6   r   r7   objectr
   r;   r   rW   r:   lenri   )r   mock_runtmp_pathr<   r=   r:   ro   r   s           r"   test_main_calls_requests_postz.TestMainWithMock.test_main_calls_requests_post   s     $@@B	H	x	'..td.KK	 #	 *5|DE	%JJ||Yg%hi	% LL*,<c(mL	% /	:		% ?H""$	% 	% 	% 	% ##q(((''	ADYq\ARUVAV)A,""6*=il1o\efg\h\l\lms\t?y|l&&v.G*1w{{62&rt#N'Gv%NN#4K#DTF!KK!	% 	% 	% 	% 	% 	% 	% 	%sT   F"(%FF
E>	0F
8F F">FF

FFF	F""F+c           	         | j                         |_        |dz  dz  j                  dd       t               }d|_        t        dg d      5  t        j                  dd	d
d      5  t        j                  t        dt        |            5  t        d|      5 }t        j                          ddd       ddd       ddd       ddd       j                  dk\  sJ |j                  d   }|j                  di       }|j                  d      dk(  s
J d|        y# 1 sw Y   rxY w# 1 sw Y   vxY w# 1 sw Y   zxY w# 1 sw Y   ~xY w)uB   --chat-id 커스텀 값 전달 시 requests.post payload에 반영r   r   Tr   r-   r   )r	   ztask-1.1r   12345r.   r0   r   r   r   r1   r2   Nre   rl   chat_idr   u   chat_id가 12345가 아님: )r   rS   r   r   r6   r   r7   r   r
   r;   r   rW   r:   ri   )r   r   r   r<   r=   r?   ro   s          r"   test_main_custom_chat_idz)TestMainWithMock.test_main_custom_chat_id   sH     $@@B	H	x	'..td.KK	 #	 *XY	%JJ||Yg%hi	% LL*,<c(mL	% /	:		% ?H""$	% 	% 	% 	% ##q((())!,//&"-{{9%0Z4PQXPY2ZZ0	% 	% 	% 	% 	% 	% 	% 	%sT   E(%D9D-D!	0D-8D9 E!D*&D--D62D99E	>EEc           	         | j                         |_        |dz  dz  j                  dd       t               }d|_        t        dddg      5  t        j                  d	d
dd      5  t        j                  t        dt        |            5  t        d|      5  t        j                          ddd       ddd       ddd       ddd       |j                  D cg c]*  }t        |d   d   t              s|d   d   d   dk(  s)|, }}t        |      dk(  s
J d|        y# 1 sw Y   yxY w# 1 sw Y   }xY w# 1 sw Y   xY w# 1 sw Y   xY wc c}w )u?   main() 실행 시 cokacdir 명령이 호출되지 않아야 함r   r   Tr   r-   r   r	   r   r.   r0   r   r   r   r1   r2   Nr   cokacdiru!   cokacdir 호출이 남아있음: )r   rS   r   r   r6   r   r7   r   r
   r;   r   rr   r   r   r   )r   r   r   r<   r   cokacdir_callss         r"   test_main_no_cokacdir_calledz-TestMainWithMock.test_main_no_cokacdir_called  s\     $@@B	H	x	'..td.KK	 #	 *5|DE	%JJ||Yg%hi	% LL*,<c(mL	% /	:		% ""$	% 	% 	% 	% &.%<%<w
1Q4PQ7TX@Y^_`a^bcd^efg^hlv^v!ww>"a']+L^L\)]]'	% 	% 	% 	% 	% 	% 	% 	% xsf   E(%E D4D(	0D48E  EE:E	E(D1-D44D=9E  E		EEr   )	r$   r%   r&   r'   r   r   r   r   r   r(   r)   r"   r|   r|      s^    9* L L6 [ [, ^ ^r)   r|   c                   d    e Zd ZdZ ed      d        Z ed      d        Z ed      d        Zy)TestDoneNotifiedMarkeru-   .done.notified 마커 파일 생성 테스트r   c           	      .   d }||_         |dz  dz  }|j                  dd       t               }d|_        t	        ddd	g      5  t	        j
                  d
ddd      5  t	        j                  t        dt        |            5  t	        d|      5  t        j                          ddd       ddd       ddd       ddd       |dz  }|j                         s
J d|        y# 1 sw Y   AxY w# 1 sw Y   ExY w# 1 sw Y   IxY w# 1 sw Y   MxY w)u5   알림 성공 시 .done.notified 마커 파일 생성c                 L   t               }d|_        d|_        t        | t              r_dj                  d | D              }d|v r%d|v r!t        j                  ddd d d      |_        |S t        j                  d	d
i      |_        |S t        j                  d	d
i      |_        |S )Nr   r   r   c              3   2   K   | ]  }t        |        y wr   r   r   s     r"   r   zkTestDoneNotifiedMarker.test_done_notified_marker_created_on_success.<locals>.side_effect.<locals>.<genexpr>:  r   r   r   r   Fr~   r   r   r   r   r   r   r>   r   s        r"   rS   zXTestDoneNotifiedMarker.test_done_notified_marker_created_on_success.<locals>.side_effect5      [F !FFM#t$(("73"77%0W5G$(JJ%*u$`de%FM M %)JJ$/?$@FM M !%

Hd+; <Mr)   r   r   Tr   r-   r   r	   z
task-777.1r.   r0   r   r   r   r1   r2   Nztask-777.1.done.notifiedu.   .done.notified 마커가 생성되지 않음: )rS   r   r   r6   r   r7   r   r
   r;   r   exists)r   r   r   rS   
events_dirr<   notified_paths          r"   ,test_done_notified_marker_created_on_successzCTestDoneNotifiedMarker.test_done_notified_marker_created_on_success1  s   	   +(83
5K	 #	 *5|DE	%JJ||Yg%hi	% LL*,<c(mL	% /	:		% ""$	% 	% 	% 	% #%??##%g)WXeWf'gg%	% 	% 	% 	% 	% 	% 	% 	%sT   D%C?C3C'	'C3/C?7D'C0,C33C<8C??D	DDc           	      J   d }||_         |dz  dz  }|j                  dd       t        j                  j	                         D ci c]  \  }}|dk7  s|| }}}d|d<   t        d	d
dg      5  t        j                  d|d      5  t        j                  t        dt        |            5  t        j                          ddd       ddd       ddd       |dz  }|j                         r
J d|        yc c}}w # 1 sw Y   ?xY w# 1 sw Y   CxY w# 1 sw Y   GxY w)u=   알림 실패 시 .done.notified 마커 파일 생성 안 함c                 L   t               }d|_        d|_        t        | t              r_dj                  d | D              }d|v r%d|v r!t        j                  ddd d d      |_        |S t        j                  d	d
i      |_        |S t        j                  d	d
i      |_        |S )Nr   r   r   c              3   2   K   | ]  }t        |        y wr   r   r   s     r"   r   zoTestDoneNotifiedMarker.test_done_notified_marker_not_created_on_failure.<locals>.side_effect.<locals>.<genexpr>a  r   r   r   r   Fr~   r   r   r   r   s        r"   rS   z\TestDoneNotifiedMarker.test_done_notified_marker_not_created_on_failure.<locals>.side_effect\  r   r)   r   r   Tr   r/   r   r   r   r	   z
task-888.1r.   rB   r   Nztask-888.1.done.notifiedu4   .done.notified 마커가 불필요하게 생성됨: )rS   r   rE   rF   rG   r   r7   r   r
   r;   r   r   )	r   r   r   rS   r   rH   rI   rJ   r   s	            r"   0test_done_notified_marker_not_created_on_failurezGTestDoneNotifiedMarker.test_done_notified_marker_not_created_on_failureX  s+   	   +(83
5 !#

 0 0 2K1a?6Jq!tKK"0*5|DE	%JJ|S5	% LL*,<c(mL	%
 ""$	% 	% 	% #%?? '')q-aboap+qq)) L	% 	% 	% 	% 	% 	%sH   C;C;0D	%D.DDDD
DD	DD"c           	         d }||_         |dz  dz  }|j                  dd       |dz  }|j                          t               }d|_        t        dd	d
g      5  t        j                  dddd      5  t        j                  t        dt        |            5  t        d|      5  t        j                          ddd       ddd       ddd       ddd       y# 1 sw Y   "xY w# 1 sw Y   &xY w# 1 sw Y   *xY w# 1 sw Y   yxY w)uH   이미 마커 존재 시 중복 생성 시도해도 에러 없이 처리c                 L   t               }d|_        d|_        t        | t              r_dj                  d | D              }d|v r%d|v r!t        j                  ddd d d      |_        |S t        j                  d	d
i      |_        |S t        j                  d	d
i      |_        |S )Nr   r   r   c              3   2   K   | ]  }t        |        y wr   r   r   s     r"   r   zkTestDoneNotifiedMarker.test_done_notified_marker_oexcl_no_duplicate.<locals>.side_effect.<locals>.<genexpr>  r   r   r   r   Fr~   r   r   r   r   s        r"   rS   zXTestDoneNotifiedMarker.test_done_notified_marker_oexcl_no_duplicate.<locals>.side_effect  r   r)   r   r   Tr   ztask-555.1.done.notifiedr-   r   r	   z
task-555.1r.   r0   r   r   r   r1   r2   N)rS   r   touchr   r6   r   r7   r   r
   r;   r   )r   r   r   rS   r   r   r<   s          r"   ,test_done_notified_marker_oexcl_no_duplicatezCTestDoneNotifiedMarker.test_done_notified_marker_oexcl_no_duplicate~  s   	   +(83
5 #%??K	 #	 *5|DE	%JJ||Yg%hi	% LL*,<c(mL	% /	:		% ""$	% 	% 	% 	% 	% 	% 	% 	% 	% 	% 	% 	%sT   D4%C5C)'C	<C)C5DC&"C))C2.C55C>	:DD
N)r$   r%   r&   r'   r   r   r   r   r(   r)   r"   r   r   .  sW    7
$h $hL #r #rJ &% &%r)   r   c                   "    e Zd ZdZd Zd Zd Zy)TestDispatchNextPhaseWithTaskIdui   dispatch_next_phase 결과에서 task_id를 받아 dispatch 명령에 --task-id를 추가하는 테스트c                    dddddd}|j                  d      }|j                  d      }|j                  d	d
      }|j                  d      }t        }|rd| nd}d| d| d| d| d| | }d|v s
J d|        d|v s
J d|        y)ug   dispatch_next_phase 결과에 task_id가 있으면 dispatch 명령에 --task-id 인자가 추가된다.dispatchmemory/tasks/task-566.2.md	dev1-team
scoped-566
task-566.2action	task_fileteamr   r   r   r   levelnormalr    --task-id r   source /.env.keys && python3 /dispatch.py --team  --task-file 	 --level 	--task-id(   dispatch 명령에 --task-id가 없음: )   dispatch 명령에 task-566.2가 없음: Nri   
_WORKSPACE	r   dispatch_resultr   r   r   r   workspace_roottask_id_argdispatch_cmds	            r"   ,test_dispatch_next_phase_result_with_task_idzLTestDispatchNextPhaseWithTaskId.test_dispatch_next_phase_result_with_task_id  s     !5$#
 $''4	""6*##GX6&**95#6BL>2n%%;N;K LV=9UGK=R 	
 l*e.VWcVd,ee*|+g/XYeXf-gg+r)   c                     ddddd}|j                  d      }|j                  d      }|j                  dd	      }|j                  d
      }t        }|rd| nd}d| d| d| d| d| | }d|vs
J d|        y)uZ   dispatch_next_phase 결과에 task_id가 없으면 dispatch 명령에 --task-id가 없다.r   zmemory/tasks/task-567.2.mdr   z
scoped-567)r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   u'   task_id 없는데 --task-id가 있음: Nr   r   s	            r"   /test_dispatch_next_phase_result_without_task_idzOTestDispatchNextPhaseWithTaskId.test_dispatch_next_phase_result_without_task_id  s     !5$	
 $''4	""6*##GX6&**95#6BL>2n%%;N;K LV=9UGK=R 	
 ,.h2YZfYg0hh.r)   c           
      8  	 ddl }d 	|dz  dz  }|j                  dd       g 	fd}t               }d	|_        t	        j
                  t        d
      5 }t	        j
                  t        dt        |            5  t	        dddg      5  t	        j                  dddd      5  t	        d|      5  ||j                  _
        |j                  |_        t        j                          ddd       ddd       ddd       ddd       ddd       t              dk\  sJ d       dj                  d d   D              }d|v s
J d|        d|v s
J d|        y# 1 sw Y   vxY w# 1 sw Y   zxY w# 1 sw Y   ~xY w# 1 sw Y   xY w# 1 sw Y   xY w)uh   실제 notify_completion의 dispatch 호출이 --task-id를 포함하는지 확인 (shell=False 방식).r   Nc                    t               }d|_        t        | t              rdj	                  d | D              }d|v r$d|v r t        j                  dddd	d
      |_        nd|v r%d|v r!t        j                  ddddd	d      |_        n[d|v rt        j                  dd	d      |_        n9t        j                  ddi      |_        nt        j                  ddi      |_        d|_        |S )Nr   r   c              3   2   K   | ]  }t        |        y wr   r   r   s     r"   r   zrTestDispatchNextPhaseWithTaskId.test_notify_completion_dispatch_cmd_format.<locals>.side_effect.<locals>.<genexpr>  r   r   r   r   TFr   r   r~   nextr   r   r   r   dispatch.py
dispatched)r   r   r   r   r   )	r   r   r   r   r   rl   r   r   r   r   s        r"   rS   z_TestDispatchNextPhaseWithTaskId.test_notify_completion_dispatch_cmd_format.<locals>.side_effect  s    [F !F#t$(("73"77%0W5G$(JJ%)egst%FM (72v7H$(JJ&0)E$/(4'3%FM #g-$(JJ,S_/`$aFM$(JJ$/?$@FM $

Hd+; <FMMr)   r   r   Tr   c                     t        | t              r+ddj                  d | D              v rj                  |         | g|i |S )Nr   r   c              3   2   K   | ]  }t        |        y wr   r   r   s     r"   r   z|TestDispatchNextPhaseWithTaskId.test_notify_completion_dispatch_cmd_format.<locals>.capturing_side_effect.<locals>.<genexpr>  s     BWa3q6BWr   )r   r   r   append)r   r   r   dispatch_callsrS   s      r"   capturing_side_effectziTestDispatchNextPhaseWithTaskId.test_notify_completion_dispatch_cmd_format.<locals>.capturing_side_effect  sG    #t$#((BWSVBW:W)W%%c*s4T4V44r)   r-   
subprocessr   r   r	   z
task-568.1r.   r0   r   r   r1   r2   re   u   dispatch.py 호출이 없음r   c              3   2   K   | ]  }t        |        y wr   r   r   xs     r"   r   z]TestDispatchNextPhaseWithTaskId.test_notify_completion_dispatch_cmd_format.<locals>.<genexpr>  s     $GSV$Gr   r   r   r   r   )r   r   r   r6   r   r   r
   r;   r7   runrS   TimeoutExpiredr   r   r   )
r   r   real_subprocessr   r   r<   mock_subdispatch_cmd_fullr   rS   s
           @@r"   *test_notify_completion_dispatch_cmd_formatzJTestDispatchNextPhaseWithTaskId.test_notify_completion_dispatch_cmd_format  s   ,	: (83
5!	5
 K	 #	 LL*L9		%=ELL*,<c(mL		% *5|DE		% JJ||Yg%hi			%
 /	:		% (=HLL$&5&D&DH#""$		% 		% 		% 		% 		% >"a'G)GG'HH$G^A5F$GG//o3[\m[n1oo/00q4]^o]p2qq0		% 		% 		% 		% 		% 		% 		% 		% 		% 		%sl   %F=FE8&E,	47E +E,	3E8;FF E)%E,	,E51E88F=FF		FFN)r$   r%   r&   r'   r   r   r  r(   r)   r"   r   r     s    sh0i.=rr)   r   c                   R    e 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)TestShellInjectionPreventionu<   셸 인젝션 방지 테스트 (Fenrir P0 위협 1-B 대응)c           
        	
 ddl }d 
g 		
fd}t               }d|_        t        j                  t
        d      5 }t        j                  t
        dt        |            5  t        dd	d
g      5  t        j                  dddd      5  t        d|      5  ||j                  _	        |j                  |_
        t
        j                          ddd       ddd       ddd       ddd       ddd       	D cg c]6  }t        |t              st        |      dk\  s#|d   dk(  s,|d   dk(  s5|8 }}t        |      dk(  s
J d|        	D cg c]<  }t        |t              r*t        |      dk\  r|d   dk(  rt        d |D              r|> }}t        |      dk\  sJ d       y# 1 sw Y   xY w# 1 sw Y   xY w# 1 sw Y   xY w# 1 sw Y   xY w# 1 sw Y   xY wc c}w c c}w )u\   dispatch 호출이 ['bash', '-c', ...] 대신 ['python3', ...] 리스트 방식인지 확인r   Nc                    t               }d|_        d|_        t        | t              rdj                  d | D              }d|v r%d|v r!t        j                  ddd	d
d      |_        |S d|v r&d|v r"t        j                  ddddd
d      |_        |S t        j                  ddi      |_        |S t        j                  ddi      |_        |S )Nr   r   r   c              3   2   K   | ]  }t        |        y wr   r   r   s     r"   r   zgTestShellInjectionPrevention.test_dispatch_uses_list_not_bash_c.<locals>.side_effect.<locals>.<genexpr>&  r   r   r   r   TFz
scoped-100z
task-100.2r~   r   r   zmemory/tasks/task-100.2.mdr   r   )r   r   r   r   r   r   r   r   r   s        r"   rS   zTTestShellInjectionPrevention.test_dispatch_uses_list_not_bash_c.<locals>.side_effect!  s    [F !FFM#t$(("73"77%0W5G$(JJ(,',(4,8	%FM, M (72v7H$(JJ&0)E$/%-'3%FM M %)JJ$/?$@FM M !%

Hd+; <Mr)   c                 >    j                  |         | g|i |S r   )r   )r   r   r   	all_callsrS   s      r"   r   z^TestShellInjectionPrevention.test_dispatch_uses_list_not_bash_c.<locals>.capturing_side_effectB  s&    S!s4T4V44r)   r-   r   r   r   r	   
task-100.1r.   r0   r   r   r1   r2   rU   bashre   z-cu7   bash -c 호출이 남아있음 (셸 인젝션 위험): python3c              3   6   K   | ]  }d t        |      v   yw)r   Nr   r   s     r"   r   zRTestShellInjectionPrevention.test_dispatch_uses_list_not_bash_c.<locals>.<genexpr>\  s     PtmnQ^befgbhQhPts   u.   python3 dispatch.py 리스트 호출이 없음)r   r   r6   r   r   r
   r;   r7   r  rS   r  r   r   r   r   any)r   r   r  r   r<   r  r   bash_c_callspython3_dispatch_callsr  rS   s            @@r"   "test_dispatch_uses_list_not_bash_cz?TestShellInjectionPrevention.test_dispatch_uses_list_not_bash_c  s   ,	> 		5 K	 #	 LL*L9		%=ELL*,<c(mL		% *5|DE		% JJ||Yg%hi			%
 /	:		% (=HLL$&5&D&DH#""$		% 		% 		% 		% 		% $-xa
1d0CARSXYZ[X\`fXfklmnkoswkwxx< A%o)`am`n'oo%
 "
!T"s1v{qty7HSPtrsPtMt "
 "

 )*a/a1aa/+		% 		% 		% 		% 		% 		% 		% 		% 		% 		% y"
s   %G"G1GF:	7F.F:	G G(G<G*G*!G**G*3G*AG/.F73F:	:G?GGGG	GG'c                     t        j                  t              5  t        j	                  dddd       ddd       y# 1 sw Y   yxY w)uB   team에 셸 메타문자(;rm -rf /)가 있으면 ValueError 발생z	;rm -rf /memory/tasks/task-1.1.mdr   Nr   r   r   r   r   r   
ValueErrorr
   _validate_dispatch_argsr   s    r"   2test_validate_dispatch_args_rejects_shell_metacharzOTestShellInjectionPrevention.test_validate_dispatch_args_rejects_shell_metachar`  B    ]]:& 	55 4!	 6 	 	 		   =Ac                     t        j                  t              5  t        j	                  dddd       ddd       y# 1 sw Y   yxY w)u<   task_file에 셸 메타문자가 있으면 ValueError 발생r   z$memory/tasks/task-1.1.md; echo pwnedr   Nr  r  r  s    r"   6test_validate_dispatch_args_rejects_task_file_metacharzSTestShellInjectionPrevention.test_validate_dispatch_args_rejects_task_file_metacharj  sB    ]]:& 	55 @!	 6 	 	 	r  c                     t        j                  t              5  t        j	                  dddd       ddd       y# 1 sw Y   yxY w)uH   level이 허용값(normal/critical/security) 외이면 ValueError 발생r   r  HACKEDNr  r  r  s    r"   1test_validate_dispatch_args_rejects_invalid_levelzNTestShellInjectionPrevention.test_validate_dispatch_args_rejects_invalid_levelt  r  r  c                     t        j                  t              5  t        j	                  dddd       ddd       y# 1 sw Y   yxY w)uH   next_task_id가 task-숫자.숫자 형식이 아니면 ValueError 발생r   r  r   ztask-1.1; drop table tasksr  Nr  r  s    r"   8test_validate_dispatch_args_rejects_invalid_next_task_idzUTestShellInjectionPrevention.test_validate_dispatch_args_rejects_invalid_next_task_id~  sB    ]]:& 	55 49	 6 	 	 	r  c                 6    t         j                  dddd       y)u.   정상적인 인자값은 예외 없이 통과r   r   r   r   r  Nr
   r  r  s    r"   )test_validate_dispatch_args_accepts_validzFTestShellInjectionPrevention.test_validate_dispatch_args_accepts_valid  s!    112%	 	2 	
r)   c                 6    t         j                  dddd       y)u"   next_task_id=None은 정상 통과r   r   criticalNr  r(  r  s    r"   4test_validate_dispatch_args_accepts_valid_no_task_idzQTestShellInjectionPrevention.test_validate_dispatch_args_accepts_valid_no_task_id  s!    112	 	2 	
r)   c                 D    t         j                  dt         ddd       y)u   level=security도 정상 통과z
alpha-teamz/memory/tasks/task-1.1.mdsecurityztask-1.2r  N)r
   r  r   r  s    r"   2test_validate_dispatch_args_accepts_security_levelzOTestShellInjectionPrevention.test_validate_dispatch_args_accepts_security_level  s)    11#$=>#	 	2 	
r)   c                     |dz  }|j                  dd       t        j                  t        |            }|j	                  d      dk(  sJ |j	                  d      dk(  sJ |j	                  d	      d
k(  sJ |j	                  d      dk(  sJ y)u   export KEY=VALUE 형식 파싱	.env.keysun   export API_KEY=abc123
export DB_PASS="secret"
export TOKEN='mytoken'
# 이것은 주석

PLAIN_VAR=plainvalue
utf-8encodingAPI_KEYabc123DB_PASSsecretTOKENmytoken	PLAIN_VAR
plainvalueN)
write_textr
   load_env_keysr;   ri   r   r   env_filer>   s       r"   !test_load_env_keys_parses_exportsz>TestShellInjectionPrevention.test_load_env_keys_parses_exports  s    k)%  	 	
 #00X?zz)$000zz)$000zz'"i///zz+&,666r)   c                     |dz  }|j                  dd       t        j                  t        |            }d|v sJ t	        |      dk(  sJ y)u   주석(#)과 빈 줄은 무시r1  u&   # 주석

export VALID_KEY=validvalue
r2  r3  	VALID_KEYre   N)r=  r
   r>  r;   r   r?  s       r"   .test_load_env_keys_ignores_comments_and_blankszKTestShellInjectionPrevention.test_load_env_keys_ignores_comments_and_blanks  s[    k)= 	 	
 #00X?f$$$6{ar)   c                 T    t         j                  t        |dz              }|i k(  sJ y)u+   파일이 없으면 빈 딕셔너리 반환znonexistent.envN)r
   r>  r;   )r   r   r>   s      r"   test_load_env_keys_missing_filez<TestShellInjectionPrevention.test_load_env_keys_missing_file  s)    "00X@Q5Q1RS||r)   N)r$   r%   r&   r'   r  r  r!  r$  r&  r)  r,  r/  rA  rD  rF  r(   r)   r"   r  r    s?    FAbF


7$	 r)   r  c                   D    e Zd ZdZd Zd Zd Zd Z ed      d        Z	y)	TestSaveCompletionMessageu6   _save_completion_message 함수 테스트 (task-923.1)c                    t        j                  t        dt        |            5  |dz  dz  }|j	                  dd       t        j                  dd       ddd       |dz  dz  d	z  j                  d
      }|dk(  sJ y# 1 sw Y   ,xY w)u,   메시지를 completion.txt 파일로 저장r   r   r   Tr   r  u   테스트 완료 메시지Nztask-100.1.completion.txtr2  r3  )r   r   r
   r;   r   _save_completion_message	read_textr   r   r   saveds       r"   test_saves_message_to_filez4TestSaveCompletionMessage.test_saves_message_to_file  s    \\+-=s8}M 	c!H,x7JTD966|Eab	c
 H$x/2MMXXbiXj4444	c 	cs   2BBc                     t        j                  t        dt        |            5  t        j	                  dd       ddd       |dz  dz  dz  j                         sJ y# 1 sw Y   %xY w)u.   events 디렉토리가 없으면 자동 생성r   z
task-200.1rD   Nr   r   ztask-200.1.completion.txt)r   r   r
   r;   rJ  r   r   r   s     r"   test_creates_parent_directoryz7TestSaveCompletionMessage.test_creates_parent_directory  se    \\+-=s8}M 	R66|[Q	R 8#h.1LLTTVVV	R 	Rs   A  A)c                 <   |dz  dz  }|j                  dd       |dz  j                  dd       t        j                  t        d	t        |            5  t        j                  d
d       ddd       |dz  j                  d      }|dk(  sJ y# 1 sw Y   &xY w)u'   기존 파일이 있으면 덮어쓰기r   r   Tr   ztask-300.1.completion.txtu   이전 메시지r2  r3  r   z
task-300.1u   새 메시지N)r   r=  r   r   r
   r;   rJ  rK  rL  s       r"   test_overwrites_existing_filez7TestSaveCompletionMessage.test_overwrites_existing_file  s    (83
5	1	1==>P[b=c\\+-=s8}M 	V66|_U	V 99DDgDV'''		V 	Vs   BBc                    t        j                  t        dd      5  t        j                  t        dt	        d            5  t        j                  dd       ddd       ddd       y# 1 sw Y   xY w# 1 sw Y   yxY w)	u'   OSError 발생 시 예외 없이 처리r   z/nonexistent/readonly/pathr   zPermission deniedrR   z
task-400.1rD   N)r   r   r
   r   OSErrorrJ  rP  s     r"   test_handles_oserror_gracefullyz9TestSaveCompletionMessage.test_handles_oserror_gracefully  ss    \\+-=?[\ 	VdGAT9UV V!::<UV	V 	VV V	V 	Vs#   &A6A*A6*A3	/A66A?r   c           	      d   d }||_         |dz  dz  }|j                  dd       t               }d|_        t	        ddd	g      5  t	        j
                  d
ddd      5  t	        j                  t        dt        |            5  t	        d|      5  t        j                          ddd       ddd       ddd       ddd       |dz  }|j                         sJ d       |j                  d      }d	|v sJ d|v sJ y# 1 sw Y   \xY w# 1 sw Y   `xY w# 1 sw Y   dxY w# 1 sw Y   hxY w)uD   main() 일반 경로에서 .completion.txt가 생성되는지 확인c                 L   t               }d|_        d|_        t        | t              r_dj                  d | D              }d|v r%d|v r!t        j                  ddd d d      |_        |S t        j                  d	d
i      |_        |S t        j                  d	d
i      |_        |S )Nr   r   r   c              3   2   K   | ]  }t        |        y wr   r   r   s     r"   r   zrTestSaveCompletionMessage.test_completion_file_created_in_main_normal_path.<locals>.side_effect.<locals>.<genexpr>  r   r   r   r   Fr~   r   r   r   r   s        r"   rS   z_TestSaveCompletionMessage.test_completion_file_created_in_main_normal_path.<locals>.side_effect  r   r)   r   r   Tr   r-   r   r	   z
task-923.1r.   r0   r   r   r   r1   r2   Nztask-923.1.completion.txtu-   .completion.txt 파일이 생성되지 않음r2  r3  r   )rS   r   r   r6   r   r7   r   r
   r;   r   r   rK  )r   r   r   rS   r   r<   completion_filecontents           r"   0test_completion_file_created_in_main_normal_pathzJTestSaveCompletionMessage.test_completion_file_created_in_main_normal_path  sB   	   +(83
5K	 #	 *5|DE	%JJ||Yg%hi	% LL*,<c(mL	% /	:		% ""$	% 	% 	% 	% %'BB%%'Y+XY'!++W+=w&&&7"""	% 	% 	% 	% 	% 	% 	% 	%sT   D&%DDD	'D/D7D&DDDDD#	D&&D/N)
r$   r%   r&   r'   rN  rQ  rS  rV  r   r\  r(   r)   r"   rH  rH    s5    @5W
(V &# &#r)   rH  )%r'   rl   rE   syspathlibr   unittest.mockr   r   rF   ri   r   r   r   __file__parent_SCRIPTS_DIRpathinsertr;   importlib.util	importlib_MODULE_PATHutilspec_from_file_locationspecmodule_from_specr
   loaderexec_moduler   r+   r|   r   r   r  rH  r(   r)   r"   <module>rn     s=    	 
  *

1<@ZZ^^,.CD
  H~$$++ 3|$ % 44~~--.A<P NN33D9 {{    ) *( ("P Pf_^ _^Dw% w%tor ordi iXN# N#r)   