
    Li                        d Z ddlZddlmc mZ ddlZddlZddl	Z	ddl
Z
ddl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j0                         d        Z ej0                         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       '/home/jay/workspace/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                  } |       }|sddt	        j
                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      d	z  }t        t        j                  |            d
x}}t        j                  |j                  d            }|d   }	d}|	|k(  }
|
slt        j                  d|
fd|	|f      t        j                  |	      t        j                  |      dz  }dd|iz  }t        t        j                  |            d
x}	x}
}|d   }	d}|	|k(  }
|
slt        j                  d|
fd|	|f      t        j                  |	      t        j                  |      dz  }dd|iz  }t        t        j                  |            d
x}	x}
}|d   }	d}|	|k(  }
|
slt        j                  d|
fd|	|f      t        j                  |	      t        j                  |      dz  }dd|iz  }t        t        j                  |            d
x}	x}
}|d   }	d}|	|k(  }
|
slt        j                  d|
fd|	|f      t        j                  |	      t        j                  |      dz  }dd|iz  }t        t        j                  |            d
x}	x}
}|d   }	g }|	|k(  }
|
slt        j                  d|
fd|	|f      t        j                  |	      t        j                  |      dz  }dd|iz  }t        t        j                  |            d
x}	x}
}y
)uW   cmd_create로 체인 파일이 생성되고 필수 필드가 올바르게 설정된다.zmy-chainu   테스트 체인iddescr   r   zmy-chain.jsonzAassert %(py4)s
{%(py4)s = %(py2)s
{%(py2)s = %(py0)s.exists
}()
}rU   py0py2py4NrI   rJ   rB   ==z%(py1)s == %(py4)spy1rg   assert %(py6)spy6r6   r8   r,   rC   r   rE   )argparse	Namespace
cmd_createexists@py_builtinslocals
@pytest_ar_should_repr_global_name	_safereprAssertionError_format_explanationrQ   loads	read_text_call_reprcompare)selfrX   r   argsrU   @py_assert1@py_assert3@py_format5rT   @py_assert0@py_assert2@py_format7s               r   test_create_chain_filez!TestCreate.test_create_chain_file   sH   !!Z6HIT"(83oE
  " """"""""z"""z""" """"""""""zz*...@AJ-:-:----:------:-------M"8&88"&88888"&8888"888&88888888H~))~))))~)))~))))))))))'(-A-(A----(A---(---A-------H~##~####~###~##########r    c                    t        j                  dd      }|j                  |       |j                         }d}|j                  }||v }|st        j                  d|fd||f      t        j                  |      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      dz  }	d	d
|	iz  }
t        t        j                  |
            dx}x}}y)u?   cmd_create 성공 시 stdout에 [OK] 메시지가 출력된다.zok-chainu   OK 메시지 테스트ra   z[OK]inz+%(py1)s in %(py5)s
{%(py5)s = %(py3)s.out
}capturedrl   py3py5assert %(py7)spy7N)ro   rp   rq   
readouterroutru   r|   rw   rs   rt   rv   rx   ry   r}   rX   r   capsysr~   r   r   @py_assert4r   @py_format6@py_format8s              r   test_create_prints_ok_messagez(TestCreate.test_create_prints_ok_message   s    !!Z6NOT"$$&%%v%%%%v%%%v%%%%%%%%%%%%%%%%%%%r    c                    t        j                  dd      }|j                  |       t        j                  t
              5 }|j                  |       ddd       j                  }|j                  }d}||k(  }|st        j                  d|fd||f      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      t        j                  |      d	z  }	d
d|	iz  }
t        t        j                  |
            dx}x}x}}y# 1 sw Y   xY w)u<   같은 ID로 두 번 생성 시 sys.exit(1)이 발생한다.z	dup-chainu   중복 테스트ra   N   rh   zG%(py4)s
{%(py4)s = %(py2)s
{%(py2)s = %(py0)s.value
}.code
} == %(py7)sexc_infore   rf   rg   r   assert %(py9)spy9)ro   rp   rq   pytestraises
SystemExitvaluecoderu   r|   rs   rt   rv   rw   rx   ry   r}   rX   r   r~   r   r   r   @py_assert6@py_assert5r   @py_format10s              r   test_create_duplicate_exitsz&TestCreate.test_create_duplicate_exits   s    !![7IJT"]]:& 	'(  &	'~~'~""'a'"a''''"a''''''x'''x'''~'''"'''a'''''''	' 	's   EEN)__name__
__module____qualname____doc__r   r   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)ro   rp   rq   )r}   rX   rB   r~   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
            }|d   }t        |      }d}	||	k(  }
|
st        j                  d|
fd||	f      dt        j                         v st        j                  t              rt        j                  t              ndt        j                  |      t        j                  |      t        j                  |	      dz  }dd|iz  }t        t        j                   |            dx}x}x}
}	|d   d   d   }d}||k(  }|slt        j                  d|fd||f      t        j                  |      t        j                  |      dz  }dd|iz  }t        t        j                   |            dx}x}}|d   d   d   }t        |      }d}	||	k(  }
|
st        j                  d|
fd||	f      dt        j                         v st        j                  t              rt        j                  t              ndt        j                  |      t        j                  |      t        j                  |	      dz  }dd|iz  }t        t        j                   |            dx}x}x}
}	y)uH   Phase 추가 후 phases 배열에 정확히 1개 항목이 추가된다.r/      작업 설명r4   rc   
base-chainr-   r   r>   r?   r   r   base-chain.jsonrI   rJ   rE   r   rh   )z0%(py4)s
{%(py4)s = %(py0)s(%(py2)s)
} == %(py7)slenr   r   r   Nr   r>   rj   rk   rm   rn   r?   )r   rQ   rR   ro   rp   cmd_add_phaserz   r{   r   ru   r|   rs   rt   rv   rw   rx   ry   )r}   rX   r   
tasks_jsonr~   rU   rT   r   r   r   r   r   r   r   r   r   r   s                    r   test_add_phase_normalz"TestAddPhase.test_add_phase_normal   s   9%ZZ+!O PQ
!!9JW%(836GG
zz*...@A>'s>"'a'"a''''"a''''''s'''s'''>'''"'''a'''''''H~a (5I5(I5555(I555(555I5555555>!$W-3s-.3!3.!3333.!333333s333s333-333.333!3333333r    c                     | 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   }d}	||	u }
|
slt        j                  d|
fd||	f      t        j                  |      t        j                  |	      dz  }dd|iz  }t        t        j                  |            dx}x}
}	|d   }d}	||	k(  }
|
slt        j                  d|
fd||	f      t        j                  |      t        j                  |	      dz  }dd|iz  }t        t        j                  |            dx}x}
}	|d   }d}	||	k(  }
|
slt        j                  d|
fd||	f      t        j                  |      t        j                  |	      dz  }dd|iz  }t        t        j                  |            dx}x}
}	|d   }d}	||	u }
|
slt        j                  d|
fd||	f      t        j                  |      t        j                  |	      dz  }dd|iz  }t        t        j                  |            dx}x}
}	|d   }d}	||	u }
|
slt        j                  d|
fd||	f      t        j                  |      t        j                  |	      dz  }dd|iz  }t        t        j                  |            dx}x}
}	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   Nisz%(py1)s is %(py4)srk   rm   rn   r8   r@   rh   rj   r7   r1   r9   r:   r   rQ   rR   ro   rp   r   rz   r{   ru   r|   rw   rx   ry   r}   rX   r   r   r~   rU   rT   taskr   r   r   r   r   s                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#+t+#t++++#t+++#+++t+++++++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(  }
|
slt        j                  d|
fd||	f      t        j                  |      t        j                  |	      dz  }dd|iz  }t        t        j                  |            dx}x}
}	y)uG   level=critical 지정 시 정규화 후에도 critical이 보존된다.r/   u   중요 작업critical)r4   rc   r7   r   zCritical Phaser   r   r   r   rI   rJ   rE   r   r?   r7   rh   rj   rk   rm   rn   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(  }|st        j                  d|fd||f      d	t        j                         v st        j                  |      rt        j                  |      nd	t        j                  |      t        j                  |      t        j                  |      d
z  }	dd|	iz  }
t        t        j                   |
            dx}x}x}}y# 1 sw Y   xY w)u9   tasks JSON 파싱 실패 시 sys.exit(1)이 발생한다.r   z	Bad Phasez{not valid json}r   Nr   rh   r   r   r   r   r   )r   ro   rp   r   r   r   r   r   r   ru   r|   rs   rt   rv   rw   rx   ry   r   s              r   test_invalid_json_exitsz$TestAddPhase.test_invalid_json_exits  s    9%!!;N`a]]:& 	*(##D)	*~~'~""'a'"a''''"a''''''x'''x'''~'''"'''a'''''''	* 	*s   EEc                    | j                  |       t        j                  ddig      }t        j                  dd|      }t        j                  t              5 }|j                  |       ddd       j                  }|j                  }d}||k(  }	|	st        j                  d|	fd	||f      d
t        j                         v st        j                  |      rt        j                   |      nd
t        j                   |      t        j                   |      t        j                   |      dz  }
dd|
iz  }t#        t        j$                  |            dx}x}x}	}y# 1 sw Y   xY w)uK   task 항목에 team 필드가 누락된 경우 sys.exit(1)이 발생한다.rc   u
   팀 없음r   zNo Team Phaser   Nr   rh   r   r   r   r   r   r   rQ   rR   ro   rp   r   r   r   r   r   r   ru   r|   rs   rt   rv   rw   rx   ry   r}   rX   r   r   r~   r   r   r   r   r   r   r   s               r   test_missing_team_field_exitsz*TestAddPhase.test_missing_team_field_exits	  s    9%ZZ&,!7 89
!!?R\]]]:& 	*(##D)	*~~'~""'a'"a''''"a''''''x'''x'''~'''"'''a'''''''	* 	*   E  E)c                    | j                  |       t        j                  ddig      }t        j                  dd|      }t        j                  t              5 }|j                  |       ddd       j                  }|j                  }d}||k(  }	|	st        j                  d|	fd	||f      d
t        j                         v st        j                  |      rt        j                   |      nd
t        j                   |      t        j                   |      t        j                   |      dz  }
dd|
iz  }t#        t        j$                  |            dx}x}x}	}y# 1 sw Y   xY w)uK   task 항목에 desc 필드가 누락된 경우 sys.exit(1)이 발생한다.r4   r/   r   zNo Desc Phaser   Nr   rh   r   r   r   r   r   r   r   s               r   test_missing_desc_field_exitsz*TestAddPhase.test_missing_desc_field_exits  s    9%ZZ&+!6 78
!!?R\]]]:& 	*(##D)	*~~'~""'a'"a''''"a''''''x'''x'''~'''"'''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(  }	|	st        j                  d	|	fd
||f      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      t        j                  |      dz  }
dd|
iz  }t!        t        j"                  |            dx}x}x}	}y# 1 sw Y   xY w)uK   존재하지 않는 체인에 Phase 추가 시 sys.exit(1)이 발생한다.r/      작업r   nonexistent-chainzPhase Xr   Nr   rh   r   r   r   r   r   )rQ   rR   ro   rp   r   r   r   r   r   r   ru   r|   rs   rt   rv   rw   rx   ry   r   s               r   test_chain_not_found_exitsz'TestAddPhase.test_chain_not_found_exits  s    ZZ+x!H IJ
!!(;)S]^]]:& 	*(##D)	*~~'~""'a'"a''''"a''''''x'''x'''~'''"'''a'''''''	* 	*s   EE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)r}   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(  }	|	slt        j                  d|	fd||f      t        j                  |      t        j                  |      dz  }
dd|
iz  }t        t        j                  |            dx}x}	}|d   }d}||u}	|	slt        j                  d|	fd||f      t        j                  |      t        j                  |      dz  }
dd|
iz  }t        t        j                  |            dx}x}	}y)uS   task-done 호출 시 해당 task의 status=completed, completed_at이 설정된다.r   r   
test-chainr0   r   r   test-chain.jsonrI   rJ   rE   r   r?   r8   	completedrh   rj   rk   rm   rn   Nr:   is notz%(py1)s is not %(py4)s)rV   r   ro   rp   cmd_task_donerQ   rz   r{   ru   r|   rw   rx   ry   )r}   rX   r   r~   rU   rT   r   r   r   r   r   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#/4/#4////#4///#///4///////r    c                    t        |       |dz  dz  |_        t        j                  dd      }|j	                  |       |j                         }d}|j                  }||v }|st        j                  d|fd||f      t        j                  |      d	t        j                         v st        j                  |      rt        j                  |      nd	t        j                  |      d
z  }	dd|	iz  }
t        t        j                  |
            dx}x}}|dz  dz  dz  }t        j                   |j#                  d            }|d   }d}||k(  }|slt        j                  d|fd||f      t        j                  |      t        j                  |      dz  }dd|iz  }t        t        j                  |            dx}x}}|d   d   d   }d}||k(  }|slt        j                  d|fd||f      t        j                  |      t        j                  |      dz  }dd|iz  }t        t        j                  |            dx}x}}y)ud   일부 task만 완료 시 '남음' 메시지가 출력되고 Phase 전환이 일어나지 않는다.r   r   r   r0   r   u   남음r   r   r   r   r   r   Nr   rI   rJ   rC   r   rh   rj   rk   rm   rn   rE   r8   r.   )rV   r   ro   rp   r   r   r   ru   r|   rw   rs   rt   rv   rx   ry   rQ   rz   r{   )r}   rX   r   r   r~   r   r   r   r   r   r   rU   rT   r   r   r   s                   r   test_not_all_complete_waitsz(TestTaskDone.test_not_all_complete_waitsB  s    *'(2X=	!!:F%$$&'8<<'x<''''x<'''x''''''8'''8'''<'''''''(836GG
zz*...@A'(-A-(A----(A---(---A-------H~a *;m;*m;;;;*m;;;*;;;m;;;;;;;r    c                    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(  }|slt        j                  d|fd|
|f      t        j                   |
      t        j                   |      dz  }dd|iz  }t#        t        j$                  |            dx}
x}}|	d   d   d   }
d}|
|k(  }|slt        j                  d|fd|
|f      t        j                   |
      t        j                   |      dz  }dd|iz  }t#        t        j$                  |            dx}
x}}|	d   }
d}|
|k(  }|slt        j                  d|fd|
|f      t        j                   |
      t        j                   |      dz  }dd|iz  }t#        t        j$                  |            dx}
x}}y# 1 sw Y   x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   rh   rj   rk   rm   rn   r   r.   rC   )rV   r   r   rQ   rR   r   objectrunreturn_valuero   rp   r   rz   r{   ru   r|   rw   rx   ry   )r}   rX   r   r   r   mock_subargs1args2rU   rT   r   r   r   r   r   s                  r   %test_all_tasks_done_transitions_phasez2TestTaskDone.test_all_tasks_done_transitions_phaseR  s    *'(2X=	00DJJ	:?V4WX\\)\2 		+h(3HLL% &&\
KE##E* &&\
KE##E*		+ (836GG
zz*...@AH~a *9k9*k9999*k999*999k9999999H~a *;m;*m;;;;*m;;;*;;;m;;;;;;;'(-A-(A----(A---(---A-------		+ 		+s   A"I==Jc                 p   |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(  }|slt%        j&                  d|fd ||f      t%        j(                  |      t%        j(                  |      d!z  }d"d#|iz  }t+        t%        j,                  |            dx}x}}|
d$   }d}||u}|slt%        j&                  d%|fd&||f      t%        j(                  |      t%        j(                  |      d!z  }d"d#|iz  }t+        t%        j,                  |            dx}x}}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   rh   rj   rk   rm   rn   r:   r   r   )rM   r   rN   rO   rP   rQ   rR   r   r   r   r   r   r   ro   rp   r   rz   r{   ru   r|   rw   rx   ry   )r}   rX   r   r   rS   rT   rU   r   r   r~   updatedr   r   r   r   r   s                   r   $test_last_phase_done_completes_chainz1TestTaskDone.test_last_phase_done_completes_chainj  s   (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/ K//// K/// ///K///////~&2d2&d2222&d222&222d2222222	* 	*s   :H++H5c                 p   |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 }|st        j                  d|fd|	|
f      t        j                  |	      dt!        j"                         v st        j$                  |      rt        j                  |      ndt        j                  |
      dz  }dd|iz  }t'        t        j(                  |            dx}	x}}
t        j*                  |j-                  d            }|d    d   d!   d   d"   }	d
}|	|k(  }|slt        j                  d#|fd$|	|f      t        j                  |	      t        j                  |      d%z  }d&d'|iz  }t'        t        j(                  |            dx}	x}}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   r   )z+%(py1)s in %(py5)s
{%(py5)s = %(py3)s.err
}r   r   r   r   rE   r?   r8   rh   rj   rk   rm   rn   )rM   r   rN   rO   rP   rQ   rR   r   ro   rp   r   r   errru   r|   rw   rs   rt   rv   rx   ry   rz   r{   )r}   rX   r   r   rS   rT   rU   r~   r   r   r   r   r   r   r   r   r   r   s                     r   #test_paused_chain_ignores_task_donez0TestTaskDone.test_paused_chain_ignores_task_done  s   (83
5 '0!"",,.224 &+ %0'1+3%-&3-9,0

0  "55
djjEBWU)	!!ZH%$$&'8<<'x<''''x<'''x''''''8'''8'''<''''''' **Z1171CDx #G,Q/9J]J9]JJJJ9]JJJ9JJJ]JJJJ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(  }|st        j                  d|fd	||f      d
t        j                         v st        j                  |      rt        j                  |      nd
t        j                  |      t        j                  |      t        j                  |      dz  }	dd|	iz  }
t!        t        j"                  |
            dx}x}x}}y# 1 sw Y   xY w)uC   존재하지 않는 task_id 완료 시 sys.exit(1)이 발생한다.r   r   r   z	task-99.9r   Nr   rh   r   r   r   r   r   )rV   r   ro   rp   r   r   r   r   r   r   ru   r|   rs   rt   rv   rw   rx   ry   r   s              r   test_task_not_found_exitsz&TestTaskDone.test_task_not_found_exits  s     *'(2X=	!!;G]]:& 	*(##D)	*~~'~""'a'"a''''"a''''''x'''x'''~'''"'''a'''''''	* 	*s   	EEc                    |dz  dz  |_         t        j                  dd      }t        j                  t
              5 }|j                  |       ddd       j                  }|j                  }d}||k(  }|st        j                  d|fd	||f      d
t        j                         v st        j                  |      rt        j                  |      nd
t        j                  |      t        j                  |      t        j                  |      dz  }	dd|	iz  }
t        t        j                   |
            dx}x}x}}y# 1 sw Y   xY w)uO   존재하지 않는 체인에 task-done 호출 시 sys.exit(1)이 발생한다.r   r   ghost-chainr0   r   Nr   rh   r   r   r   r   r   )r   ro   rp   r   r   r   r   r   r   ru   r|   rs   rt   rv   rw   rx   ry   r   s              r   r   z'TestTaskDone.test_chain_not_found_exits  s    '(2X=	!!JG]]:& 	*(##D)	*~~'~""'a'"a''''"a''''''x'''x'''~'''"'''a'''''''	* 	*s   EE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(  }|slt        j                  d|fd|
|f      t        j                  |
      t        j                  |      dz  }dd|iz  }t!        t        j"                  |            dx}
x}}d}
|
|	v }|st        j                  d|fd|
|	f      t        j                  |
      dt%        j&                         v st        j(                  |	      rt        j                  |	      nddz  }dd|iz  }t!        t        j"                  |            dx}
}y# 1 sw Y   `xY w)uc   Phase 전환 시 dispatch 실패가 발생하면 chain status=paused, error 필드가 설정된다.r   r   r    zerror messager   r   r0   r   r<   Nr   rI   rJ   r8   r   rh   rj   rk   rm   rn   errorr   z%(py1)s in %(py3)srT   rl   r   assert %(py5)sr   )rV   r   r   r   r   r   r   ro   rp   r   rQ   rz   r{   ru   r|   rw   rx   ry   rs   rt   rv   )r}   rX   r   r   mock_error_resultr   r   r   rU   rT   r   r   r   r   r   @py_format4r   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~))~))))~)))~))))))))))w$w$w$$		+ 		+s   A"HH)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                    t        j                  dd      }|j                  |       |j                          t        j                  d      }|j	                  |       |j                         }t        j                  |j                        }d}||v }	|	st        j                  d|	fd||f      t        j                  |      dt        j                         v st        j                  |      rt        j                  |      ndd	z  }
d
d|
iz  }t        t        j                  |            dx}}	|d   }d}||k(  }	|	slt        j                  d|	fd||f      t        j                  |      t        j                  |      dz  }dd|iz  }t        t        j                  |            dx}x}	}d}||v }	|	st        j                  d|	fd||f      t        j                  |      dt        j                         v st        j                  |      rt        j                  |      ndd	z  }
d
d|
iz  }t        t        j                  |            dx}}	y)uO   cmd_status 실행 시 JSON 출력에 chain_id와 status 필드가 포함된다.zstatus-chainu   상태 확인 테스트ra   r   rB   r   r   outputr  r  r   Nrh   rj   rk   rm   rn   r8   )ro   rp   rq   r   
cmd_statusrQ   rz   r   ru   r|   rw   rs   rt   rv   rx   ry   )r}   rX   r   r   create_argsstatus_argsr   r
  r   r   r  r   r   r   r   s                  r   test_status_outputzTestStatus.test_status_output  sq   ((NAZ[[)((~>[)$$&HLL)#zV####zV###z######V###V#######j!3^3!^3333!^333!333^3333333!x6!!!!x6!!!x!!!!!!6!!!6!!!!!!!r    c                    t        j                  d      }t        j                  t              5 }|j                  |       ddd       j                  }|j                  }d}||k(  }|st        j                  d|fd||f      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      t        j                  |      dz  }	d	d
|	iz  }
t        t        j                  |
            dx}x}x}}y# 1 sw Y   xY w)uL   존재하지 않는 체인의 status 조회 시 sys.exit(1)이 발생한다.r   r	  Nr   rh   r   r   r   r   r   )ro   rp   r   r   r   r  r   r   ru   r|   rs   rt   rv   rw   rx   ry   r   s              r   test_status_nonexistent_exitsz(TestStatus.test_status_nonexistent_exits  s    !!6]]:& 	'(  &	'~~'~""'a'"a''''"a''''''x'''x'''~'''"'''a'''''''	' 	's   D55D>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 }|st        j                  d|fd||f      t        j                  |      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      dz  }	dd|	iz  }
t        t        j                  |
            dx}x}}y)	uO   체인이 없을 때 '활성 체인이 없습니다' 메시지가 출력된다.u   활성 체인이 없습니다r   r   r   r   r   r   N)ro   rp   cmd_listr   r   ru   r|   rw   rs   rt   rv   rx   ry   r   s              r   test_list_emptyzTestList.test_list_empty  s    !!#4 $$&.>(,,>.,>>>>.,>>>.>>>>>>(>>>(>>>,>>>>>>>r    c                 T   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 }|st        j                  d|fd||
f      t        j                  |      d	t        j                         v st        j                  |
      rt        j                  |
      nd	d
z  }dd|iz  }t        t        j                   |            dx}}d}||
v }|st        j                  d|fd||
f      t        j                  |      d	t        j                         v st        j                  |
      rt        j                  |
      nd	d
z  }dd|iz  }t        t        j                   |            dx}}d}||
v }|st        j                  d|fd||
f      t        j                  |      d	t        j                         v st        j                  |
      rt        j                  |
      nd	d
z  }dd|iz  }t        t        j                   |            dx}}yc c}	w )uG   여러 체인 생성 후 목록에 모두 포함되는지 확인한다.   zchain-u   체인 ra   rB   zchain-0r   r   	chain_idsr  r  r   Nzchain-1zchain-2)rangero   rp   rq   r   r  rQ   rz   r   ru   r|   rw   rs   rt   rv   rx   ry   )r}   rX   r   r   ir  	list_argsr   r
  itemr  r   r   r  r   s                  r   test_list_multiple_chainsz"TestList.test_list_multiple_chains  s   q 	.A",,&WQC=QK  -	. 	&&(	9%$$&HLL)289$T*%9	9%yI%%%%yI%%%y%%%%%%I%%%I%%%%%%%%yI%%%%yI%%%y%%%%%%I%%%I%%%%%%%%yI%%%%yI%%%y%%%%%%I%%%I%%%%%%% :s   'J%c                 n   t        j                  dd      }|j                  |       |j                          t        j                         }|j	                  |       |j                         }t        j                  |j                        }t        |      }d}	||	k(  }
|
st        j                  d|
fd||	f      dt        j                         v st        j                  t              rt        j                  t              nddt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |	      d	z  }d
d|iz  }t        t        j                   |            dx}x}
}	|d   }d}||v }|st        j                  d|fd||f      t        j                  |      dt        j                         v st        j                  |      rt        j                  |      nddz  }dd|iz  }t        t        j                   |            dx}}d}||v }|st        j                  d|fd||f      t        j                  |      dt        j                         v st        j                  |      rt        j                  |      nddz  }dd|iz  }t        t        j                   |            dx}}d}||v }|st        j                  d|fd||f      t        j                  |      dt        j                         v st        j                  |      rt        j                  |      nddz  }dd|iz  }t        t        j                   |            dx}}d}||v }|st        j                  d|fd||f      t        j                  |      dt        j                         v st        j                  |      rt        j                  |      nddz  }dd|iz  }t        t        j                   |            dx}}y)u[   목록의 각 항목에 chain_id, status, current_phase_idx, total_phases 필드가 있다.zfield-chainu   필드 확인ra   r   rh   z0%(py3)s
{%(py3)s = %(py0)s(%(py1)s)
} == %(py6)sr   r
  re   rl   r   rn   assert %(py8)spy8Nr   rB   r   r   r  r  r  r   r8   rC   total_phases)ro   rp   rq   r   r  rQ   rz   r   r   ru   r|   rs   rt   rv   rw   rx   ry   )r}   rX   r   r   r  r  r   r
  r   r   r   r   @py_format9r  r   r  r   s                    r   test_list_shows_correct_fieldsz'TestList.test_list_shows_correct_fields/  sf   ((MP[)&&(	9%$$&HLL)6{a{a{ass66{aay!zT!!!!zT!!!z!!!!!!T!!!T!!!!!!!x4x4x44"*"d****"d***"******d***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   )r}   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(  }|slt        j                  d|fd||f      t        j                  |      t        j                  |      dz  }	dd|	iz  }
t        t        j                  |
            dx}x}}|d   }d}||k(  }|slt        j                  d|fd||f      t        j                  |      t        j                  |      dz  }	dd|	iz  }
t        t        j                  |
            dx}x}}|d   }d}||u}|slt        j                  d|fd||f      t        j                  |      t        j                  |      dz  }	dd|	iz  }
t        t        j                  |
            dx}x}}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   rh   rj   rk   rm   rn   Nr8   r.   r9   r   r   )
r)  _update_chain_taskrQ   rz   r{   ru   r|   rw   rx   ry   )r}   r]   r   rU   rT   r   r   r   r   r   r   s              r   #test_update_chain_task_sets_task_idz7TestUpdateChainTask.test_update_chain_task_sets_task_idk  sY   $$X.''ZP(836II
zz*...@AH~a )!,I,*,*,,,,*,,,,,,*,,,,,,,H~..~....~...~..........O$0D0$D0000$D000$000D0000000r    c                 *    |j                  ddd       y)ub   존재하지 않는 체인 파일일 때 에러 없이 경고만 출력하고 정상 종료된다.r   r/   r0   N)r,  )r}   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(  }
|
slt        j                  d|
fd||	f      t        j                  |      t        j                  |	      dz  }d d!|iz  }t        t        j                  |            dx}x}
}	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   rh   rj   rk   rm   rn   )rM   r   rN   rO   rP   rQ   rR   r,  rz   r{   ru   r|   rw   rx   ry   )r}   r]   r   rS   rT   rU   r   r   r   r   r   r   r   s                r   :test_update_chain_task_does_not_overwrite_existing_task_idzNTestUpdateChainTask.test_update_chain_task_does_not_overwrite_existing_task_id}  sC   (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"44444"4444444"4444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(  }|slt        j                  d |fd!|
|f      t        j                  |
      t        j                  |      d"z  }d#d$|iz  }t        t        j                  |            dx}
x}}|d%   }
d
}|
|k(  }|slt        j                  d |fd!|
|f      t        j                  |
      t        j                  |      d"z  }d#d$|iz  }t        t        j                  |            dx}
x}}|	d   }
d}|
|u }|slt        j                  d&|fd'|
|f      t        j                  |
      t        j                  |      d"z  }d#d$|iz  }t        t        j                  |            dx}
x}}|	d%   }
d}|
|k(  }|slt        j                  d |fd!|
|f      t        j                  |
      t        j                  |      d"z  }d#d$|iz  }t        t        j                  |            dx}
x}}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   r4  s     r   r7  zPTestUpdateChainTask.test_update_chain_task_correct_team_match.<locals>.<genexpr>  r8  r9  r5   rh   rj   rk   rm   rn   r8   r   r   )rM   r   rN   rO   rP   rQ   rR   r,  rz   r{   nextru   r|   rw   rx   ry   )r}   r]   r   rS   rT   rU   r   r?   	dev1_task	dev2_taskr   r   r   r   r   s                  r   )test_update_chain_task_correct_team_matchz=TestUpdateChainTask.test_update_chain_task_correct_team_match  so   (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444#}444#444}4444444"3m3"m3333"m333"333m3333333#+t+#t++++#t+++#+++t+++++++"/i/"i////"i///"///i///////r    N)r+  )	r   r   r   r   r)  r-  r/  r1  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   )r}   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                 L   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&t'        j(                  d|fd|
|f      dt+        j,                         v st'        j.                  t$              rt'        j0                  t$              nddt+        j,                         v st'        j.                  |	      rt'        j0                  |	      ndt'        j0                  |
      t'        j0                  |      dz  }t'        j2                  dt!        |j5                                      dz   d|iz  }t7        t'        j8                  |            dx}
x}}|	d   j                  d      }||k(  }|st'        j(                  d|fd||f      dt+        j,                         v st'        j.                  |      rt'        j0                  |      nddt+        j,                         v st'        j.                  |      rt'        j0                  |      nddz  }dd|iz  }t7        t'        j8                  |            d}y# 1 sw Y   Rx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.mdr   rh   r  r   
task_filesr   uD   태스크 파일이 생성되지 않았습니다. tasks_dir 내용: 
>assert %(py8)sr"  z%(py0)s == %(py2)scontentr6   re   rf   zassert %(py4)srg   )rB  r   r   rQ   rz   r{   r   r   rR   r   r   r   r   r   r   _dispatch_phaser   globr   ru   r|   rs   rt   rv   rw   _format_assertmsgiterdirrx   ry   )r}   rX   r   r6   rU   rT   r   r   	tasks_dirrH  r   r   r   r   r$  rK  r   @py_format3r   s                      r   $test_dispatch_phase_writes_task_filez>TestDispatchPhaseTaskFile.test_dispatch_phase_writes_task_file  s   9,,X;,O'(2X=	&	(83oE
zz*...@Ak!"!ZZJ(?@\\)\2 	;h(3HLL%%%dAz:	;
 x''1	)..)MNO
:  	H!  	H!#  	H  	H  	H!  	H  	H  	H  	H  	H  	Hs  	H  	H  	Hs  	H  	H  	H  	H  	H  	H:  	H  	H  	H:  	H  	H  	H  	H  	H  	H!  	H  	H  	H'klpqz  rC  rC  rE  mF  lG  &H  	H  	H  	H  	H  	H  	H  	H Q-))7);+%%%%w+%%%%%%w%%%w%%%%%%+%%%+%%%%%%%	; 	;s   $%LL#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                   }|st#        j$                  d      dz   dt'        j(                         v st#        j*                  |      rt#        j,                  |      ndt#        j,                  |      t#        j,                  |      dz  }	t/        t#        j0                  |	            dx}}|j                  j2                  }
|
d   d   }d}||v }|st#        j4                  d|fd||f      t#        j,                  |      dt'        j(                         v st#        j*                  |      rt#        j,                  |      nddz  }t#        j$                  d|       dz   d|iz  }t/        t#        j0                  |            dx}}g }d}||v}|}|s ||j7                  d      dz
     }d}||k7  }|}|sDt#        j4                  d|fd||f      t#        j,                  |      dt'        j(                         v st#        j*                  |      rt#        j,                  |      nddz  }d d!|iz  }|j9                  |       |s_t#        j4                  d"fd#f      t#        j,                  |      t#        j,                  |      d$z  }d%d&|iz  }|j9                  |       t#        j:                  |d      i z  }t#        j$                  d'|       d(z   d)|iz  }t/        t#        j0                  |            dx}x}x}x}x}x}}y# 1 sw Y   )xY w)*uS   _dispatch_phase 호출 시 subprocess.run에 --task-file 플래그가 사용된다.u$   task-file 플래그 확인용 작업rD  r   r   rE  rI   rJ   r   r5   ztask-9.2r   r   rF  Nu/   subprocess.run이 호출되지 않았습니다.zE
>assert %(py4)s
{%(py4)s = %(py2)s
{%(py2)s = %(py0)s.run
}.called
}r   rd   z--task-filer   r   r  r  u.   --task-file 플래그가 cmd에 없습니다: z
>assert %(py5)sr   z--taskr   )not in)z%(py3)s not in %(py5)s)r   r   z%(py7)sr   )!=)z%(py10)s != %(py13)s)py10py13z%(py15)spy15u:   구식 --task 플래그가 여전히 cmd에 있습니다: z
>assert %(py18)spy18)rB  r   r   rQ   rz   r{   r   r   rR   r   r   r   r   r   r   rM  calledru   rO  rs   rt   rv   rw   rx   ry   	call_argsr|   indexappend_format_boolop)r}   rX   r   rU   rT   r   r   r   r   r   r\  r  r   r   r  r   r   @py_assert9@py_assert12@py_assert11r   @py_format14@py_format16@py_format17@py_format19s                            r   'test_dispatch_phase_uses_task_file_flagzATestDispatchPhaseTaskFile.test_dispatch_phase_uses_task_file_flag  s`   ,,XCi,j'(2X=	&	(83oE
zz*...@Ak!"!ZZJ(?@\\)\2 	;h(3HLL%%%dAz:	; ||U|""U"UU$UUUUUUUxUUUxUUU|UUU"UUUUUULL**	Q<?e}(eee}eee}eeeeeeeeeeeee,Z[cZd*eeeeeee	S	SH$	S(01NQR1R(S	SW_	S(SW_(_	S 	SARAR	SH	S 	SIR 	S 	SLRF	S 	S:R:R %	S 	SIR %	S 	S 	SLRF	SLR	SARAR	S(SW_	S 	SIR )T	S 	SIR X`	S 	S 	SLRF	SLR	SDRN	S 	SARARGzR	S 	S 	S?R?R	S 	S 	S 	S	; 	;s   "%O..O8c                 R   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t'        j(                  d|fd|
|f      dt+        j,                         v st'        j.                  t$              rt'        j0                  t$              nddt+        j,                         v st'        j.                  |	      rt'        j0                  |	      ndt'        j0                  |
      t'        j0                  |      dz  }t'        j2                  d      dz   d|iz  }t5        t'        j6                  |            dx}
x}}|	d	   j                  d      }||k(  }|st'        j(                  d|fd||f      dt+        j,                         v st'        j.                  |      rt'        j0                  |      nddt+        j,                         v st'        j.                  |      rt'        j0                  |      nddz  }t'        j2                  d|d|      d z   d!|iz  }t5        t'        j6                  |            d}y# 1 sw Y   TxY w)"ui   특수문자(', ", $, 백틱)가 포함된 description이 파일을 통해 깨지지 않고 저장된다.u|   특수문자 테스트: 작은따옴표('), 큰따옴표("), 달러($VAR), 백틱(`cmd`), 줄바꿈
두 번째 줄도 포함rF  )rB   r6   r   r   rE  rI   rJ   r   r5   ztask-9.3r   r   Nr?   rG  r   rh   r  r   rH  r   uT   특수문자 description에 대한 태스크 파일이 생성되지 않았습니다.rI  r"  rJ  rK  special_descriptionrL  u=   파일 내용이 원본 description과 다릅니다.
예상: u	   
실제: z
>assert %(py4)srg   )rB  r   r   rQ   rz   r{   r   r   rR   r   r   r   r   r   r   rM  r   rN  r   ru   r|   rs   rt   rv   rw   rO  rx   ry   )r}   rX   r   ri  rU   rT   r   r   rQ  rH  r   r   r   r   r$  rK  r   rR  r   s                      r   !test_dispatch_phase_special_charsz;TestDispatchPhaseTaskFile.test_dispatch_phase_special_chars8  s   ,,+ 	- 	

  ((2X=	&	(83oE
zz*...@Ak!"!ZZJ(?@\\)\2 	;h(3HLL%%%dAz:	; x''1	)..)MNO
:{!{!#{{{!{{{{{{s{{{s{{{{{{:{{{:{{{{{{!{{{%{{{{{{{{Q-))7);**	ygxgx	y*	y 	yrxrx	y 	y`x`x 	y 	yoxox 	y 	yrxrx	y 	y`x`x +	y 	yoxox +	y 	ygxgxKL_Kbblmtlwx	y 	y 	yexex	y 	y	; 	;s   %%LL&N)rF  r   )r   r   r   r   rB  rS  rg  rj  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)r}   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   rn  N)ro  r   r   r   r   r   r   r   r   r   TimeoutExpiredrp  assert_called_once)r}   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   rr  rv  r   r    r   rl  rl  _  s    E-.r    rl  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   r   r   
ValueErrorrM  r}   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 hackedrz  r1   r@   r{  r|  r   zsafe-chain-idNr}  r  s        r   -test_dispatch_phase_rejects_injection_in_teamzGTestShellInjectionDefense.test_dispatch_phase_rejects_injection_in_team  s    (83
5)	&	 % %=+1%-&/	

  ]]:& 	@%%dA?	@ 	@ 	@r  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(  }
|
st        j                   d|
fd||	f      dt#        j$                         v st        j&                  t              rt        j(                  t              nddt#        j$                         v st        j&                  |      rt        j(                  |      ndt        j(                  |      t        j(                  |	      dz  }dd|iz  }t+        t        j,                  |            dx}x}
}	|d   d   }d}||k(  }|slt        j                   d|fd||f      t        j(                  |      t        j(                  |      d z  }d!d"|iz  }t+        t        j,                  |            dx}x}}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-123r   rh   r  r   resultsr   r!  r"  Nr8   
dispatchedrj   rk   rm   rn   )rM   r   r   r   r   rQ   rR   r   r   r   r   r   r   rM  r   ru   r|   rs   rt   rv   rw   rx   ry   )r}   rX   r   rS   rT   r   r   r  r   r   r   r   r$  r   r   r   s                   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$$$$<1$$$$$$3$$$3$$$$$$w$$$w$$$<$$$1$$$$$$$1:h'7<7'<7777'<777'777<7777777	8 	8 	8s   F8II%N)r   r   r   r   r  r  r  r   r    r   rx  rx    s    ;>4@4"8r    rx  )r   )$r   builtinsrs   _pytest.assertion.rewrite	assertionrewriteru   ro   rQ   r   r   r   typesr   pathlibr   unittest.mockr   r   r   
ModuleTyper   r&   rV   fixturerX   r]   r_   r   r   r  r  r'  r@  rl  rx  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    