
    i                        d Z ddlZddl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m	Z	 ddl
mZmZ ddlZde	dej                  fdZde	dej                  fd	Zdd
Z ej$                         d        Z ej$                         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 G d d      Z G d d      Z G d d      Zy) u  
test_chain.py

chain.py Phase 자동 체이닝 시스템 단위 테스트 (아르고스 작성)

테스트 항목:
- TestCreate: 체인 파일 생성, 중복 ID 에러
- TestAddPhase: Phase 추가, tasks 스키마 정규화, 잘못된 JSON 에러
- TestTaskDone: 완료 마킹, 미완료 대기, Phase 전환, paused 무시
- TestStatus: JSON 출력 확인
- TestList: 빈 목록, 여러 체인 목록
- TestUpdateChainTask: dispatch.py _update_chain_task 함수 동작
    N)datetime)Path)	MagicMockpatchtmp_pathreturnc                    t        t        j                  j                  dd            }t	        |      t
        j                  vr)t
        j                  j                  dt	        |             t        t
        j                  j                               D ]  }|dk(  s	t
        j                  |=  ddl}| |_        | dz  dz  |_        |S )u   chain 모듈을 tmp_path를 WORKSPACE로 설정하여 로드한다.

    chain.py는 모듈 최상단에서 WORKSPACE를 환경변수 기준으로 결정하므로
    sys.modules에서 제거 후 WORKSPACE 패치를 적용한다.
    WORKSPACE_ROOT/home/jay/workspacer   chainNmemorychains)r   osenvirongetstrsyspathinsertlistmoduleskeysr   	WORKSPACE
CHAINS_DIR)r   	workspacemod_name_chains       A/home/jay/workspace/.worktrees/task-2116-dev1/tests/test_chain.py_load_chain_with_workspacer       s     RZZ^^$46KLMI
9~SXX%3y>*))+, &wH%& F 8+h6FM    c                    t        t        j                  j                  dd            }t	        |      t
        j                  vr)t
        j                  j                  dt	        |             ddl}t        t
        j                  j                               D ]  }|dk(  s	t
        j                  |=  ddl}| |_        |S )uF   dispatch 모듈을 tmp_path를 WORKSPACE로 설정하여 로드한다.r
   r   r   Ndispatch)r   r   r   r   r   r   r   r   prompts.team_promptsr   r   r   r"   r   )r   r   promptsr   	_dispatchs        r   _load_dispatch_with_workspacer&   5   s    RZZ^^$46KLMI
9~SXX%3y>*))+, &z!H%& !"Ir    c                 8   | dz  dz  }|j                  dd       |dddt        j                         j                         dd	d
dddd	dddddddd	dddgdddd
dddddddgdgd}|| dz  }|j	                  t        j                  |d      d       |S )u[   2개 Phase, Phase 0에 2개 in_progress task, Phase 1에 1개 pending task를 생성한다.r   r   Tparentsexist_okTestactiver   Phase 1in_progress	dev1-teamtask-1.1u   작업Anormal
2026-01-01Nteamtask_iddescriptionlevelstatusdispatched_atcompleted_at	dev2-teamtask-2.1u   작업Bnamer8   taskszPhase 2pendingu   작업Cchain_idr6   r8   current_phase_idx
created_atphases.jsonFensure_asciiutf-8encodingmkdirr   now	isoformat
write_textjsondumps)r   rB   
chains_dirdata
chain_files        r   _setup_chain_with_phasesrV   L   s    H$x/JTD1lln..0 "' !,#-'0!)"/)5(, !,#-'0!)"/)5(,2 "# !,#''0!)"+)-(,
3(
/D` 
%00J$**T>QKr    c                 J    | dz  dz  j                  dd       t        |       S )uA   격리된 WORKSPACE를 사용하는 chain 모듈을 반환한다.r   r   Tr(   )rM   r   )r   s    r   	chain_modrX      s,     8#**4$*G%h//r    c              #   R  K   | dz  j                  dd       | dz  dz  j                  dd       t        t        j                  j	                  dd            }t
        j                  j	                  d      }t        |       }| ||_        ||t
        j                  d<   yyw)	uD   격리된 WORKSPACE를 사용하는 dispatch 모듈을 반환한다.r   Tr(   r?   r
   r   r"   N)	rM   r   r   r   r   r   r   r&   r   )r   real_workspace_original_dispatchmods       r   dispatch_modr]      s      t<7"))$)F"**..)9;PQRN4
'
1C
I"CM%"4J &s   B%B'c                   "    e Zd ZdZd Zd Zd Zy)
TestCreateu&   cmd_create() 서브커맨드 테스트c                 <   t        j                  dd      }|j                  |       |dz  dz  dz  }|j                         sJ t	        j
                  |j                  d            }|d	   dk(  sJ |d
   dk(  sJ |d   dk(  sJ |d   dk(  sJ |d   g k(  sJ y)uW   cmd_create로 체인 파일이 생성되고 필수 필드가 올바르게 설정된다.zmy-chainu   테스트 체인iddescr   r   zmy-chain.jsonrI   rJ   rB   r6   r8   r,   rC   r   rE   N)argparse	Namespace
cmd_createexistsrQ   loads	read_text)selfrX   r   argsrU   rT   s         r   test_create_chain_filez!TestCreate.test_create_chain_file   s    !!Z6HIT"(83oE
  """zz*...@AJ:---M"&8888H~)))'(A---H~###r    c                     t        j                  dd      }|j                  |       |j                         }d|j                  v sJ y)u?   cmd_create 성공 시 stdout에 [OK] 메시지가 출력된다.zok-chainu   OK 메시지 테스트ra   z[OK]N)rd   re   rf   
readouterroutrj   rX   r   capsysrk   captureds         r   test_create_prints_ok_messagez(TestCreate.test_create_prints_ok_message   sC    !!Z6NOT"$$&%%%r    c                    t        j                  dd      }|j                  |       t        j                  t
              5 }|j                  |       ddd       j                  j                  dk(  sJ y# 1 sw Y   %xY w)u<   같은 ID로 두 번 생성 시 sys.exit(1)이 발생한다.z	dup-chainu   중복 테스트ra   N   )rd   re   rf   pytestraises
SystemExitvaluecoderj   rX   r   rk   exc_infos        r   test_create_duplicate_exitsz&TestCreate.test_create_duplicate_exits   sl    !![7IJT"]]:& 	'(  &	'~~""a'''	' 	's   A88BN)__name__
__module____qualname____doc__rl   rs   r}    r    r   r_   r_      s    0$&(r    r_   c                   B    e Zd ZdZddZd Zd Zd Zd Zd Z	d Z
d	 Zy
)TestAddPhaseu)   cmd_add_phase() 서브커맨드 테스트c                 T    t        j                  |d      }|j                  |       y)u2   테스트용 체인 파일을 생성하는 헬퍼.u   베이스 체인ra   N)rd   re   rf   )rj   rX   rB   rk   s       r   _create_chainzTestAddPhase._create_chain   s#    !!X4FGT"r    c                    | j                  |       t        j                  dddg      }t        j                  dd|      }|j                  |       |dz  dz  d	z  }t        j                  |j                  d
            }t        |d         dk(  sJ |d   d   d   dk(  sJ t        |d   d   d         dk(  sJ y)uH   Phase 추가 후 phases 배열에 정확히 1개 항목이 추가된다.r/      작업 설명r4   rc   
base-chainr-   r   r>   r?   r   r   base-chain.jsonrI   rJ   rE   ru   r   r>   r?   N)	r   rQ   rR   rd   re   cmd_add_phaserh   ri   len)rj   rX   r   
tasks_jsonrk   rU   rT   s          r   test_add_phase_normalz"TestAddPhase.test_add_phase_normal   s    9%ZZ+!O PQ
!!9JW%(836GG
zz*...@A4>"a'''H~a (I5554>!$W-.!333r    c                 x   | j                  |       t        j                  dddg      }t        j                  dd|      }|j                  |       |dz  dz  d	z  }t        j                  |j                  d
            }|d   d   d   d   }|d   J |d   dk(  sJ |d   dk(  sJ |d   J |d   J y)ul   task 스키마 정규화: task_id=None, status=pending, level=normal, dispatched_at=None, completed_at=None.r;   u   정규화 확인r   r   zPhase Ar   r   r   r   rI   rJ   rE   r   r?   r5   Nr8   r@   r7   r1   r9   r:   r   rQ   rR   rd   re   r   rh   ri   rj   rX   r   r   rk   rU   rT   tasks           r   test_tasks_normalizationz%TestAddPhase.test_tasks_normalization   s    9%ZZ+?Q!R ST
!!9JW%(836GG
zz*...@AH~a )!,I&&&H~***G}(((O$,,,N#+++r    c                 <   | j                  |       t        j                  ddddg      }t        j                  dd|      }|j                  |       |dz  d	z  d
z  }t        j                  |j                  d            }|d   d   d   d   }|d   dk(  sJ y)uG   level=critical 지정 시 정규화 후에도 critical이 보존된다.r/   u   중요 작업critical)r4   rc   r7   r   zCritical Phaser   r   r   r   rI   rJ   rE   r   r?   r7   Nr   r   s           r   test_tasks_level_preservedz'TestAddPhase.test_tasks_level_preserved   s    9%ZZ+Yc!d ef
!!;KS]^%(836GG
zz*...@AH~a )!,G}
***r    c                 
   | j                  |       t        j                  ddd      }t        j                  t
              5 }|j                  |       ddd       j                  j                  dk(  sJ y# 1 sw Y   %xY w)u9   tasks JSON 파싱 실패 시 sys.exit(1)이 발생한다.r   z	Bad Phasez{not valid json}r   Nru   )	r   rd   re   rv   rw   rx   r   ry   rz   r{   s        r   test_invalid_json_exitsz$TestAddPhase.test_invalid_json_exits  sn    9%!!;N`a]]:& 	*(##D)	*~~""a'''	* 	*s   A99Bc                 :   | j                  |       t        j                  ddig      }t        j                  dd|      }t        j                  t              5 }|j                  |       ddd       j                  j                  dk(  sJ y# 1 sw Y   %xY w)uK   task 항목에 team 필드가 누락된 경우 sys.exit(1)이 발생한다.rc   u
   팀 없음r   zNo Team Phaser   Nru   r   rQ   rR   rd   re   rv   rw   rx   r   ry   rz   rj   rX   r   r   rk   r|   s         r   test_missing_team_field_exitsz*TestAddPhase.test_missing_team_field_exits	  s    9%ZZ&,!7 89
!!?R\]]]:& 	*(##D)	*~~""a'''	* 	*   BBc                 :   | j                  |       t        j                  ddig      }t        j                  dd|      }t        j                  t              5 }|j                  |       ddd       j                  j                  dk(  sJ y# 1 sw Y   %xY w)uK   task 항목에 desc 필드가 누락된 경우 sys.exit(1)이 발생한다.r4   r/   r   zNo Desc Phaser   Nru   r   r   s         r   test_missing_desc_field_exitsz*TestAddPhase.test_missing_desc_field_exits  s    9%ZZ&+!6 78
!!?R\]]]:& 	*(##D)	*~~""a'''	* 	*r   c                    t        j                  dddg      }t        j                  dd|      }t	        j
                  t              5 }|j                  |       ddd       j                  j                  dk(  sJ y# 1 sw Y   %xY w)	uK   존재하지 않는 체인에 Phase 추가 시 sys.exit(1)이 발생한다.r/      작업r   nonexistent-chainzPhase Xr   Nru   )
rQ   rR   rd   re   rv   rw   rx   r   ry   rz   r   s         r   test_chain_not_found_exitsz'TestAddPhase.test_chain_not_found_exits  sw    ZZ+x!H IJ
!!(;)S]^]]:& 	*(##D)	*~~""a'''	* 	*s   BB
N)r   )r~   r   r   r   r   r   r   r   r   r   r   r   r   r    r   r   r      s-    3#
4, 
+((((r    r   c                   H    e Zd ZdZddZd Zd Zd Zd Zd Z	d	 Z
d
 Zd Zy)TestTaskDoneu)   cmd_task_done() 서브커맨드 테스트Nc                 v    t               }||_        ||nt        j                  ddi      |_        ||_        |S )u2   subprocess.run mock 결과를 생성하는 헬퍼.r5   task-3.1)r   
returncoderQ   rR   stdoutstderr)rj   r   r   r   mock_results        r   _make_subprocess_mockz"TestTaskDone._make_subprocess_mock,  s=    k!+'-'9Vtzz9V`Ja?b#r    c                 "   t        |       |dz  dz  |_        t        j                  dd      }|j	                  |       |dz  dz  dz  }t        j                  |j                  d            }|d	   d
   d   d
   }|d   dk(  sJ |d   J y)uS   task-done 호출 시 해당 task의 status=completed, completed_at이 설정된다.r   r   
test-chainr0   r   r   test-chain.jsonrI   rJ   rE   r   r?   r8   	completedr:   N)rV   r   rd   re   cmd_task_donerQ   rh   ri   )rj   rX   r   rk   rU   rT   r   s          r   test_task_done_marks_completedz+TestTaskDone.test_task_done_marks_completed4  s     *'(2X=	!!:F%(836GG
zz*...@AH~a )!,H~,,,N#///r    c                 X   t        |       |dz  dz  |_        t        j                  dd      }|j	                  |       |j                         }d|j                  v sJ |dz  dz  dz  }t        j                  |j                  d	            }|d
   dk(  sJ |d   d   d   dk(  sJ y)ud   일부 task만 완료 시 '남음' 메시지가 출력되고 Phase 전환이 일어나지 않는다.r   r   r   r0   r   u   남음r   rI   rJ   rC   r   rE   r8   r.   N)
rV   r   rd   re   r   rn   ro   rQ   rh   ri   )rj   rX   r   rq   rk   rr   rU   rT   s           r   test_not_all_complete_waitsz(TestTaskDone.test_not_all_complete_waitsB  s     *'(2X=	!!:F%$$&8<<'''(836GG
zz*...@A'(A---H~a *m;;;r    c                 N   t        |       |dz  dz  |_        | j                  dt        j                  ddi            }t        j                  |d      5 }||j                  _        t        j                  dd	      }|j                  |       t        j                  dd
	      }|j                  |       ddd       |dz  dz  dz  }t        j                  |j                  d            }	|	d   d   d   dk(  sJ |	d   d   d   dk(  sJ |	d   dk(  sJ y# 1 sw Y   dxY w)ue   모든 task 완료 시 Phase[0] completed, Phase[1] in_progress, current_phase_idx=1로 전환된다.r   r   r   r5   r   
subprocessr   r0   r   r<   Nr   rI   rJ   rE   r8   r   ru   r.   rC   )rV   r   r   rQ   rR   r   objectrunreturn_valuerd   re   r   rh   ri   )
rj   rX   r   rq   r   mock_subargs1args2rU   rT   s
             r   %test_all_tasks_done_transitions_phasez2TestTaskDone.test_all_tasks_done_transitions_phaseR  s0    *'(2X=	00DJJ	:?V4WX\\)\2 		+h(3HLL% &&\
KE##E* &&\
KE##E*		+ (836GG
zz*...@AH~a *k999H~a *m;;;'(A---		+ 		+s   A"DD$c                    |dz  dz  }|j                  dd       ddddt        j                         j                         d	d
ddddd
dddgdgd}|dz  }|j	                  t        j                  |d      d       ||_        | j                  dt        j                  ddi            }t        j                  |d      5 }||j                  _        t        j                  dd      }	|j                  |	       ddd       t        j                   |j#                  d            }
|
d   dk(  sJ |
d   J y# 1 sw Y   @xY w) u_   마지막 Phase의 모든 task 완료 시 chain status=completed, completed_at이 설정된다.r   r   Tr(   zsingle-phase-chainu   단일 Phase 체인r,   r   r-   r.   r/   r0   u   유일한 작업r1   r2   Nr3   r=   rA   zsingle-phase-chain.jsonFrG   rI   rJ   r5   r   r   r   r8   r   r:   )rM   r   rN   rO   rP   rQ   rR   r   r   r   r   r   r   rd   re   r   rh   ri   )rj   rX   r   rq   rS   rT   rU   r   r   rk   updateds              r   $test_last_phase_done_completes_chainz1TestTaskDone.test_last_phase_done_completes_chainj  se   (83
5 -0!"",,.224 &+ %0'1+=%-&3-9,0

0  ";;
djjEBWU)	00DJJ	:?V4WX\\)\2 	*h(3HLL%%%,@zRD##D)	*
 **Z1171CDx K///~&222	* 	*s   :D==Ec                    |dz  dz  }|j                  dd       ddddt        j                         j                         d	d
ddddd
dddgdgd}|dz  }|j	                  t        j                  |d      d       ||_        t        j                  dd      }|j                  |       |j                         }d|j                  v sJ t        j                  |j                  d            }	|	d   d   d   d   d   d
k(  sJ y)ub   paused 상태의 체인은 task-done을 무시하고 stderr에 'paused' 메시지를 출력한다.r   r   Tr(   zpaused-chainu   일시정지 체인pausedr   r-   r.   r/   r0   r   r1   r2   Nr3   r=   rA   zpaused-chain.jsonFrG   rI   rJ   r   rE   r?   r8   )rM   r   rN   rO   rP   rQ   rR   r   rd   re   r   rn   errrh   ri   )
rj   rX   r   rq   rS   rT   rU   rk   rr   r   s
             r   #test_paused_chain_ignores_task_donez0TestTaskDone.test_paused_chain_ignores_task_done  s6   (83
5 '0!"",,.224 &+ %0'1+3%-&3-9,0

0  "55
djjEBWU)	!!ZH%$$&8<<''' **Z1171CDx #G,Q/9]JJJr    c                    t        |       |dz  dz  |_        t        j                  dd      }t	        j
                  t              5 }|j                  |       ddd       j                  j                  dk(  sJ y# 1 sw Y   %xY w)uC   존재하지 않는 task_id 완료 시 sys.exit(1)이 발생한다.r   r   r   z	task-99.9r   Nru   )
rV   r   rd   re   rv   rw   rx   r   ry   rz   r{   s        r   test_task_not_found_exitsz&TestTaskDone.test_task_not_found_exits  sw     *'(2X=	!!;G]]:& 	*(##D)	*~~""a'''	* 	*s   	A??Bc                     |dz  dz  |_         t        j                  dd      }t        j                  t
              5 }|j                  |       ddd       j                  j                  dk(  sJ y# 1 sw Y   %xY w)uO   존재하지 않는 체인에 task-done 호출 시 sys.exit(1)이 발생한다.r   r   ghost-chainr0   r   Nru   )	r   rd   re   rv   rw   rx   r   ry   rz   r{   s        r   r   z'TestTaskDone.test_chain_not_found_exits  so    '(2X=	!!JG]]:& 	*(##D)	*~~""a'''	* 	*s   A44A=c                    t        |       |dz  dz  |_        | j                  ddd      }t        j                  |d      5 }||j
                  _        t        j                  dd	      }|j                  |       t        j                  dd
	      }|j                  |       ddd       |dz  dz  dz  }t        j                  |j                  d            }	|	d   dk(  sJ d|	v sJ y# 1 sw Y   JxY w)uc   Phase 전환 시 dispatch 실패가 발생하면 chain status=paused, error 필드가 설정된다.r   r   ru    zerror messager   r   r0   r   r<   Nr   rI   rJ   r8   r   error)rV   r   r   r   r   r   r   rd   re   r   rQ   rh   ri   )
rj   rX   r   rq   mock_error_resultr   r   r   rU   rT   s
             r    test_dispatch_error_pauses_chainz-TestTaskDone.test_dispatch_error_pauses_chain  s     *'(2X=	 66q"oN\\)\2 		+h(9HLL% &&\
KE##E* &&\
KE##E*		+ (836GG
zz*...@AH~)))$		+ 		+s   A"C--C6)r   Nr   )r~   r   r   r   r   r   r   r   r   r   r   r   r   r   r    r   r   r   )  s5    30< .0+3Z)KV((r    r   c                       e Zd ZdZd Zd Zy)
TestStatusu&   cmd_status() 서브커맨드 테스트c                 L   t        j                  dd      }|j                  |       |j                          t        j                  d      }|j	                  |       |j                         }t        j                  |j                        }d|v sJ |d   dk(  sJ d|v sJ y)uO   cmd_status 실행 시 JSON 출력에 chain_id와 status 필드가 포함된다.zstatus-chainu   상태 확인 테스트ra   r   rB   r8   N)rd   re   rf   rn   
cmd_statusrQ   rh   ro   )rj   rX   r   rq   create_argsstatus_argsrr   outputs           r   test_status_outputzTestStatus.test_status_output  s    ((NAZ[[)((~>[)$$&HLL)V###j!^3336!!!r    c                     t        j                  d      }t        j                  t              5 }|j                  |       ddd       j                  j                  dk(  sJ y# 1 sw Y   %xY w)uL   존재하지 않는 체인의 status 조회 시 sys.exit(1)이 발생한다.r   r   Nru   )rd   re   rv   rw   rx   r   ry   rz   r{   s        r   test_status_nonexistent_exitsz(TestStatus.test_status_nonexistent_exits  s[    !!6]]:& 	'(  &	'~~""a'''	' 	's   A&&A/N)r~   r   r   r   r   r   r   r    r   r   r     s    0" (r    r   c                   "    e Zd ZdZd Zd Zd Zy)TestListu$   cmd_list() 서브커맨드 테스트c                     t        j                         }|j                  |       |j                         }d|j                  v sJ y)uO   체인이 없을 때 '활성 체인이 없습니다' 메시지가 출력된다.u   활성 체인이 없습니다N)rd   re   cmd_listrn   ro   rp   s         r   test_list_emptyzTestList.test_list_empty  s>    !!#4 $$&.(,,>>>r    c                    t        d      D ]0  }t        j                  d| d|       }|j                  |       2 |j	                          t        j                         }|j                  |       |j	                         }t        j                  |j                        }|D 	cg c]  }	|	d   	 }
}	d|
v sJ d|
v sJ d|
v sJ y	c c}	w )
uG   여러 체인 생성 후 목록에 모두 포함되는지 확인한다.   zchain-u   체인 ra   rB   zchain-0zchain-1zchain-2N)	rangerd   re   rf   rn   r   rQ   rh   ro   )rj   rX   r   rq   ir   	list_argsrr   r   item	chain_idss              r   test_list_multiple_chainsz"TestList.test_list_multiple_chains  s    q 	.A",,&WQC=QK  -	. 	&&(	9%$$&HLL)289$T*%9	9I%%%I%%%I%%% :s   'Cc                 v   t        j                  dd      }|j                  |       |j                          t        j                         }|j	                  |       |j                         }t        j                  |j                        }t        |      dk(  sJ |d   }d|v sJ d|v sJ d|v sJ d	|v sJ y
)u[   목록의 각 항목에 chain_id, status, current_phase_idx, total_phases 필드가 있다.zfield-chainu   필드 확인ra   ru   r   rB   r8   rC   total_phasesN)	rd   re   rf   rn   r   rQ   rh   ro   r   )	rj   rX   r   rq   r   r   rr   r   r   s	            r   test_list_shows_correct_fieldsz'TestList.test_list_shows_correct_fields/  s    ((MP[)&&(	9%$$&HLL)6{aayT!!!4"d***%%%r    N)r~   r   r   r   r   r   r   r   r    r   r   r     s    .?&$&r    r   c                   0    e Zd ZdZddZd Zd Zd Zd Zy)	TestUpdateChainTasku4   dispatch.py의 _update_chain_task() 함수 테스트c                 
   |dz  dz  }|j                  dd       |dddt        j                         j                         dd	d
dddddddgdgd}|| dz  }|j	                  t        j                  |d      d       |S )u>   _update_chain_task 테스트용 체인 파일을 생성한다.r   r   Tr(   u   업데이트 테스트r,   r   r-   r.   r/   Nu   dispatch 대기 작업r1   r@   r3   r=   rA   rF   FrG   rI   rJ   rL   )rj   r   rB   rS   rT   rU   s         r   _setup_chain_for_updatez+TestUpdateChainTask._setup_chain_for_updateK  s    (83
5 3!"",,.224 &+ %0'++C%-&/-1,0

0  XJe"44
djjEBWUr    c                     | j                  |       |j                  ddd       |dz  dz  dz  }t        j                  |j	                  d            }|d	   d
   d   d
   }|d   dk(  sJ |d   dk(  sJ |d   J y)uK   pending task에 task_id, status=in_progress, dispatched_at이 설정된다.update-chainr/   ztask-5.1r   r   zupdate-chain.jsonrI   rJ   rE   r   r?   r5   r8   r.   r9   N)r   _update_chain_taskrQ   rh   ri   )rj   r]   r   rU   rT   r   s         r   #test_update_chain_task_sets_task_idz7TestUpdateChainTask.test_update_chain_task_sets_task_idk  s    $$X.''ZP(836II
zz*...@AH~a )!,I*,,,H~...O$000r    c                 *    |j                  ddd       y)ub   존재하지 않는 체인 파일일 때 에러 없이 경고만 출력하고 정상 종료된다.r   r/   r0   N)r   )rj   r]   r   s      r   &test_update_chain_task_chain_not_foundz:TestUpdateChainTask.test_update_chain_task_chain_not_foundx  s     	''(;[*Ur    c                    |dz  dz  }|j                  dd       ddddt        j                         j                         d	d
ddddd
dddgdgd}|dz  }|j	                  t        j                  |d      d       |j                  ddd       t        j                  |j                  d            }|d   d   d   d   }|d   dk(  sJ y)u^   이미 task_id가 설정된 task는 _update_chain_task에 의해 덮어쓰여지지 않는다.r   r   Tr(   zno-overwrite-chainu   덮어쓰기 방지 테스트r,   r   r-   r.   r/   ztask-already-setu   이미 dispatch된 작업r1   z2026-01-01T00:00:00Nr3   r=   rA   zno-overwrite-chain.jsonFrG   rI   rJ   ztask-new-idrE   r?   r5   )
rM   r   rN   rO   rP   rQ   rR   r   rh   ri   )rj   r]   r   rS   rT   rU   r   r   s           r   :test_update_chain_task_does_not_overwrite_existing_task_idzNTestUpdateChainTask.test_update_chain_task_does_not_overwrite_existing_task_id}  s   (83
5,:!"",,.224 &+ %0'9+F%-&3-B,0

0  ";;
djjEBWU 	''(<k=Y**Z1171CDx #G,Q/I"4444r    c                 ,   |dz  dz  }|j                  dd       ddddt        j                         j                         d	d
ddddddddddddddddgdgd}|dz  }|j	                  t        j                  |d      d       |j                  ddd       t        j                  |j                  d            }|d   d   d   }t        d |D              }t        d |D              }	|d   dk(  sJ |d    d
k(  sJ |	d   J |	d    dk(  sJ y)!uD   여러 팀의 task 중 지정된 team의 task만 업데이트된다.r   r   Tr(   zmulti-team-chainu   다팀 테스트r,   r   r-   r.   r/   Nu   dev1 작업r1   r@   r3   r;   u   dev2 작업r=   rA   zmulti-team-chain.jsonFrG   rI   rJ   ztask-dev1.1rE   r?   c              3   2   K   | ]  }|d    dk(  s|  yw)r4   r/   Nr   .0ts     r   	<genexpr>zPTestUpdateChainTask.test_update_chain_task_correct_team_match.<locals>.<genexpr>       FqQvY+-EF   c              3   2   K   | ]  }|d    dk(  s|  yw)r4   r;   Nr   r   s     r   r   zPTestUpdateChainTask.test_update_chain_task_correct_team_match.<locals>.<genexpr>  r   r   r5   r8   )rM   r   rN   rO   rP   rQ   rR   r   rh   ri   next)
rj   r]   r   rS   rT   rU   r   r?   	dev1_task	dev2_tasks
             r   )test_update_chain_task_correct_team_matchz=TestUpdateChainTask.test_update_chain_task_correct_team_match  sq   (83
5*-!"",,.224 &+ %0'++8%-&/-1,0 %0'++8%-&/-1,0 
B  "99
djjEBWU''(:KW**Z1171CD!!$W-FEFF	FEFF	#}444"m333#+++"i///r    N)r   )	r~   r   r   r   r   r   r   r   r  r   r    r   r   r   H  s!    >@1V
$5L30r    r   c                   *    e Zd ZdZddZd Zd Zd Zy)TestDispatchPhaseTaskFileuD   _dispatch_phase()가 --task-file 방식을 사용하는지 테스트c                 
   |dz  dz  }|j                  dd       |dddt        j                         j                         dd	d
d|dd	dddgdgd}|| dz  }|j	                  t        j                  |d      d       |S )uE   dispatch 대상 Phase가 있는 체인 파일을 생성하는 헬퍼.r   r   Tr(   u   태스크 파일 테스트r,   r   r-   r@   r/   Nr1   r3   r=   rA   rF   FrG   rI   rJ   rL   )rj   r   rB   r6   rS   rT   rU   s          r   _setup_chain_with_pending_phasez9TestDispatchPhaseTaskFile._setup_chain_with_pending_phase  s    (83
5 7!"",,.224 &' %0'++6%-&/-1,0

0  XJe"44
djjEBWUr    c                    d}| j                  ||       |dz  dz  |_        ||_        |dz  dz  dz  }t        j                  |j                  d            }t               }d|_        t        j                  d	d
i      |_	        d|_
        t        j                  |d      5 }||j                  _        |j                  |dd       ddd       |dz  dz  }t!        |j#                  d            }	t%        |	      dk(  s!J dt!        |j'                                       |	d   j                  d      }
|
|k(  sJ y# 1 sw Y   wxY w)uV   _dispatch_phase 호출 시 description이 memory/tasks/ 하위 파일로 저장된다.u!   파일로 저장될 작업 설명r6   r   r   tf-chain.jsonrI   rJ   r   r5   ztask-9.1r   r   tf-chainNr?   "chain-tf-chain-phase0-dev1-team.mdru   uD   태스크 파일이 생성되지 않았습니다. tasks_dir 내용: )r  r   r   rQ   rh   ri   r   r   rR   r   r   r   r   r   r   _dispatch_phaser   globr   iterdir)rj   rX   r   r6   rU   rT   r   r   	tasks_dir
task_filescontents              r   $test_dispatch_phase_writes_task_filez>TestDispatchPhaseTaskFile.test_dispatch_phase_writes_task_file  sm   9,,X;,O'(2X=	&	(83oE
zz*...@Ak!"!ZZJ(?@\\)\2 	;h(3HLL%%%dAz:	;
 x''1	)..)MNO
:!#  	H'klpqz  rC  rC  rE  mF  lG  &H  	H# Q-))7);+%%%	; 	;s   $%D??Ec                    | j                  |d       |dz  dz  |_        ||_        |dz  dz  dz  }t        j                  |j                  d            }t               }d|_        t        j                  d	d
i      |_	        d|_
        t        j                  |d      5 }||j                  _        |j                  |dd       ddd       j                  j                   sJ d       |j                  j"                  }|d   d   }d|v s
J d|        d|vs%||j%                  d      dz
     dk7  s
J d|        yy# 1 sw Y   |xY w)uS   _dispatch_phase 호출 시 subprocess.run에 --task-file 플래그가 사용된다.u$   task-file 플래그 확인용 작업r
  r   r   r  rI   rJ   r   r5   ztask-9.2r   r   r  Nu/   subprocess.run이 호출되지 않았습니다.z--task-fileu.   --task-file 플래그가 cmd에 없습니다: z--taskru   u:   구식 --task 플래그가 여전히 cmd에 있습니다: )r  r   r   rQ   rh   ri   r   r   rR   r   r   r   r   r   r   r  called	call_argsindex)	rj   rX   r   rU   rT   r   r   r  r   s	            r   'test_dispatch_phase_uses_task_file_flagzATestDispatchPhaseTaskFile.test_dispatch_phase_uses_task_file_flag  sg   ,,XCi,j'(2X=	&	(83oE
zz*...@Ak!"!ZZJ(?@\\)\2 	;h(3HLL%%%dAz:	; ||""U$UU"LL**	Q<?(e,Z[cZd*ee(H$1NQR1R(SW_(_	SGzR	S_(_$	; 	;s   "%EEc                 z   d}| j                  |d|       |dz  dz  |_        ||_        |dz  dz  dz  }t        j                  |j                  d            }t               }d	|_        t        j                  d
di      |_	        d|_
        t        j                  |d      5 }||j                  _        |j                  |d	d       ddd       |dz  dz  }t!        |j#                  d            }	t%        |	      dk(  sJ d       |	d	   j                  d      }
|
|k(  sJ d|d|
       y# 1 sw Y   hxY w)ui   특수문자(', ", $, 백틱)가 포함된 description이 파일을 통해 깨지지 않고 저장된다.u|   특수문자 테스트: 작은따옴표('), 큰따옴표("), 달러($VAR), 백틱(`cmd`), 줄바꿈
두 번째 줄도 포함r  )rB   r6   r   r   r  rI   rJ   r   r5   ztask-9.3r   r   Nr?   r  ru   uT   특수문자 description에 대한 태스크 파일이 생성되지 않았습니다.u=   파일 내용이 원본 description과 다릅니다.
예상: u	   
실제: )r  r   r   rQ   rh   ri   r   r   rR   r   r   r   r   r   r   r  r   r  r   )rj   rX   r   special_descriptionrU   rT   r   r   r  r  r  s              r   !test_dispatch_phase_special_charsz;TestDispatchPhaseTaskFile.test_dispatch_phase_special_chars8  sc   ,,+ 	- 	

  ((2X=	&	(83oE
zz*...@Ak!"!ZZJ(?@\\)\2 	;h(3HLL%%%dAz:	; x''1	)..)MNO
:!#{%{{#Q-))7);**	yKL_Kbblmtlwx	y*	; 	;s   %%D11D:N)r  r   )r~   r   r   r   r  r  r  r  r   r    r   r  r    s    N@&8S6yr    r  c                       e Zd ZdZd Zd Zy)TestCronNotifyGracefulSkipu;   _cron_notify() ANU_KEY 없으면 graceful skip (task-448.1)c                     d|_         t        j                  |d      5 }|j                  d       |j                  j                          ddd       y# 1 sw Y   yxY w)uJ   ANU_KEY가 비어있으면 _cron_notify가 subprocess 호출 없이 리턴r   r      테스트 알림N)ANU_KEYr   r   _cron_notifyr   assert_not_called)rj   rX   r   r   s       r   &test_cron_notify_skips_when_no_anu_keyzATestCronNotifyGracefulSkip.test_cron_notify_skips_when_no_anu_keyb  sN    	\\)\2 	-h""#56LL**,	- 	- 	-s   ,AAc                 H   d|_         t               }d|_        d|_        d|_        t        j                  |d      5 }||j                  _        t        j                  |_
        |j                  d       |j                  j                          ddd       y# 1 sw Y   yxY w)uJ   ANU_KEY가 설정되어 있으면 _cron_notify가 subprocess 정상 호출ztest-key-123r   r   r   r   N)r!  r   r   r   r   r   r   r   r   r   TimeoutExpiredr"  assert_called_once)rj   rX   r   r   r   s        r   'test_cron_notify_works_when_anu_key_setzBTestCronNotifyGracefulSkip.test_cron_notify_works_when_anu_key_setj  s    *	k!"\\)\2 	.h(3HLL%&0&?&?H#""#56LL++-		. 	. 	.s   ABB!N)r~   r   r   r   r$  r(  r   r    r   r  r  _  s    E-.r    r  c                   "    e Zd ZdZd Zd Zd Zy)TestShellInjectionDefenseu1   _dispatch_phase 쉘 인젝션 방어 (task-448.1)c                     |dz  dz  }|j                  dd       ||_        ||_        ddddd	d
dgdgi}t        j                  t
              5  |j                  |dd       ddd       y# 1 sw Y   yxY w)u?   chain_id에 '; rm -rf /' 같은 값 넣으면 ValueError 발생r   r   Tr(   rE   r-   r/   testr1   r@   r4   r6   r7   r8   r>   r?   r   z"; rm -rf /NrM   r   r   rv   rw   
ValueErrorr  rj   rX   r   rS   rT   s        r   1test_dispatch_phase_rejects_injection_in_chain_idzKTestShellInjectionDefense.test_dispatch_phase_rejects_injection_in_chain_id  s    (83
5)	&	 % %0+1%-&/	

  ]]:& 	>%%dA}=	> 	> 	>   A..A7c                     |dz  dz  }|j                  dd       ||_        ||_        ddddd	d
dgdgi}t        j                  t
              5  |j                  |dd       ddd       y# 1 sw Y   yxY w)u:   team에 인젝션 문자열을 넣으면 ValueError 발생r   r   Tr(   rE   r-   zdev1-team; echo hackedr,  r1   r@   r-  r.  r   zsafe-chain-idNr/  r1  s        r   -test_dispatch_phase_rejects_injection_in_teamzGTestShellInjectionDefense.test_dispatch_phase_rejects_injection_in_team  s    (83
5)	&	 % %=+1%-&/	

  ]]:& 	@%%dA?	@ 	@ 	@r3  c                    |dz  dz  }|j                  dd       |dz  dz  j                  dd       ||_        ||_        dddd	d
ddgdgi}t               }d|_        t        j                  ddi      |_        d|_        t        j                  |d      5 }||j                  _        |j                  |dd      }t        |      dk(  sJ |d   d   dk(  sJ 	 ddd       y# 1 sw Y   yxY w)u,   정상적인 chain_id, team, level은 통과r   r   Tr(   r?   rE   r-   r/   z
valid testr1   r@   r-  r.  r   r5   r0   r   r   zvalid-chain-123ru   r8   
dispatchedN)rM   r   r   r   r   rQ   rR   r   r   r   r   r   r   r  r   )rj   rX   r   rS   rT   r   r   resultss           r   %test_dispatch_phase_accepts_valid_idsz?TestShellInjectionDefense.test_dispatch_phase_accepts_valid_ids  s    (83
5	H	w	&--dT-J)	&	 % %0+7%-&/	

   k!"!ZZJ(?@\\)\2 	8h(3HLL%//a9JKGw<1$$$1:h'<777	8 	8 	8s   AC''C0N)r~   r   r   r   r2  r5  r9  r   r    r   r*  r*    s    ;>4@4"8r    r*  )r   )r   rd   rQ   r   r   r   typesr   pathlibr   unittest.mockr   r   rv   
ModuleTyper   r&   rV   fixturerX   r]   r_   r   r   r   r   r   r  r  r*  r   r    r   <module>r?     s
     	  
    *  %2B2B *D U5E5E .6| 0 0 5 5*!( !(RQ( Q(rA AR( (@.& .&lN0 N0lyy yyB. .@Y8 Y8r    