
    $iz                    
   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mZ ddlmZ ddlmZmZ ddlZej$                  j'                  dd      Zdefd	Zdefd
Zdede
j2                  fdZ ej6                         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% 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 d0 d1      Z. G d2 d3      Z/d4 Z0d5 Z1d6 Z2 G d7 d8      Z3 G d9 d:      Z4 G d; d<      Z5 G d= d>      Z6 G d? d@      Z7 G dA dB      Z8 G dC dD      Z9 G dE dF      Z: G dG dH      Z; G dI dJ      Z< G dK dL      Z= G dM dN      Z> G dO dP      Z? G dQ dR      Z@ G dS dT      ZA G dU dV      ZB G dW dX      ZC G dY dZ      ZDy)[u[  
test_dispatch.py

dispatch.py 단위 테스트 (아르고스 작성)

테스트 항목:
- generate_task_id(): 유니크한 ID 생성 (tmp_path + monkeypatch로 WORKSPACE 격리)
- build_prompt(): 올바른 팀 정보 포함 여부 확인
- TEAM_BOT 매핑 정확성 검증
- 잘못된 팀 ID 전달 시 에러 처리 (sys.exit 또는 ValueError)
    NdatetimePath)	MagicMockpatchCOKACDIR_CHAT_ID
6937032012returnc                     t        t        j                  j                  dd            } | dz  dz  }|j	                         s
t               S t        |d      5 }t        j                  |      }ddd       t               }j                  di       j                  d	i       j                  d
g       D ]  }|j                  d      dk(  s|j                  dg       D ]^  }|j                  dd      }|j                  d      s'|j                  d      s9|j                  d      dk7  sN|j                  |       `  |S # 1 sw Y   xY w)uD   organization-structure.json에서 dev 팀 ID 목록을 로드한다.WORKSPACE_ROOT/home/jay/workspacememoryorganization-structure.jsonutf-8encodingN	structurecolumnsteamsteam_iddevelopment-office	sub_teamssub_team_id devz-teamstatusplanned)r   osenvirongetexistssetopenjsonload
startswithendswithadd)	workspaceorg_pathforgdev_team_idsteamsubtids           */home/jay/workspace/tests/test_dispatch.py_load_dev_team_ids_from_orgr3      s!   RZZ^^$46KLMI8#&CCH??u	h	) Qiil5LR(,,Y;??L *88I"66xxR0 *ggmR0>>%(S\\'-BswwxGX\eGe $$S)**  s   EEc                  :   t        t        j                  j                  dd            } | dz  dz  }|j	                         syt        |d      5 }t        j                  |      }ddd       d	}j                  d
i       j                  di       j                  dg       D ]j  }|j                  d      dk(  r|j                  dd      }|dk(  r2|j                  dg       D ]  }|j                  d      dk7  s|dz  } a|dv sf|dz  }l |S # 1 sw Y   xY w)uP   organization-structure.json에서 전체 팀(dev + logical) 수를 계산한다.r   r   r   r   r   r   Nr   r   r   r   r   r   r   r   r   r      )marketing-teamzconsulting-teamzpublishing-teamzdesign-teamzcontent-team)r   r   r    r!   r"   r$   r%   r&   )r*   r+   r,   r-   countr/   r1   r0   s           r2   _count_all_team_info_from_orgr9   1   s   RZZ^^$46KLMI8#&CCH??	h	) QiilER(,,Y;??L 88H*hhy"%&&xxR0 778$	1QJE kkQJE L s   DDtmp_pathc                    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 )u   dispatch 모듈을 tmp_path를 WORKSPACE로 설정하여 로드한다.

    dispatch.py는 모듈 최상단에서 WORKSPACE를 __file__ 기준으로 결정하므로
    sys.modules에서 제거 후 WORKSPACE 패치를 적용한다.
    r   r   r   Ndispatch)r   r   r    r!   strsyspathinsertprompts.team_promptslistmoduleskeysr<   	WORKSPACE)r:   r*   promptsmod_name	_dispatchs        r2   _load_dispatch_with_workspacerI   N   s     RZZ^^$46KLMI
9~SXX%3y>*   ))+, &z!H%& ! #I    c              #   Z  K   ddl }| dz  j                  dd       | dz  dz  j                  dd       t        t        j                  j                  dd            }|j                  j                  d	      }t        |       }d
 |_        | ||_	        |||j                  d	<   yyw)u:   격리된 WORKSPACE를 사용하는 dispatch 모듈 반환r   Nr   Tparentsexist_oktasksr   r   r<   c                      y)NT )keys    r2   <lambda>zdispatch_mod.<locals>.<lambda>y   s    rJ   )
r>   mkdirr   r   r    r!   rC   rI   _check_bot_processrE   )r:   _sysreal_workspace_original_dispatchmods        r2   dispatch_modrZ   m   s      t<7"))$)F"**..)9;PQRN))*5
'
1C-C
I"CM%#5Z  &s   B)B+c                   (    e Zd ZdZd Zd Zd Zd Zy)TestTeamBotMappinguG   TEAM_BOT 딕셔너리가 올바른 팀→봇 매핑을 갖는지 검증c                    |j                   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 )N	dev1-teamdev1==z%(py1)s == %(py4)spy1py4assert %(py6)spy6TEAM_BOT
@pytest_ar_call_reprcompare	_safereprAssertionError_format_explanationselfrZ   @py_assert0@py_assert3@py_assert2@py_format5@py_format7s          r2   test_dev1_team_maps_to_dev1z.TestTeamBotMapping.test_dev1_team_maps_to_dev1   Z    $$[1;V;1V;;;;1V;;;1;;;V;;;;;;;rJ   c                    |j                   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 )N	dev2-teamdev2r`   rb   rc   rf   rg   rh   ro   s          r2   test_dev2_team_maps_to_dev2z.TestTeamBotMapping.test_dev2_team_maps_to_dev2   rw   rJ   c                    |j                   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 )N	dev3-teamdev3r`   rb   rc   rf   rg   rh   ro   s          r2   test_dev3_team_maps_to_dev3z.TestTeamBotMapping.test_dev3_team_maps_to_dev3   rw   rJ   c           
         t               }t        |      }d}||kD  }|st        j                  d|fd||f      dt	        j
                         v st        j                  t              rt        j                  t              nddt	        j
                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      dz  }t        j                  d      dz   d	|iz  }t        t        j                  |            d x}x}}|j                  }|j                  } |       }t        |      }	|	|k(  }
|
s_t        j                  d
|
fd|	|f      dt	        j
                         v st        j                  t              rt        j                  t              nddt	        j
                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      t        j                  |      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}x}x}x}	}
y )Nr   >)z/%(py3)s
{%(py3)s = %(py0)s(%(py1)s)
} > %(py6)slenexpected_dev_teamspy0rd   py3rg   u7   organization-structure.json에서 dev 팀 로드 실패
>assert %(py8)spy8r`   )z%(py9)s
{%(py9)s = %(py0)s(%(py7)s
{%(py7)s = %(py5)s
{%(py5)s = %(py3)s
{%(py3)s = %(py1)s.TEAM_BOT
}.keys
}()
})
} == %(py11)sr#   rZ   r   rd   r   py5py7py9py11assert %(py13)spy13)r3   r   rj   rk   @py_builtinslocals_should_repr_global_namerl   _format_assertmsgrm   rn   ri   rD   r#   )rp   rZ   r   rs   @py_assert5@py_assert4ru   @py_format9@py_assert6@py_assert8@py_assert10@py_format12@py_format14s                r2   $test_team_bot_contains_all_dev_teamsz7TestTeamBotMapping.test_team_bot_contains_all_dev_teams   s   8:%&ee&*eee&eeeeeeseeeseeeeee%eee%eee&eeeeee,eeeeeeee((F(--F-/Fs/0F04FFFFF04FFFFFFFsFFFsFFFFFF<FFF<FFF(FFF-FFF/FFF0FFFFFF4FFFF4FFFFFFFFFrJ   N)__name__
__module____qualname____doc__rv   r{   r   r   rQ   rJ   r2   r\   r\      s    Q<<<GrJ   r\   c                   4    e Zd ZdZd Zd Zd Zd Zd Zd Z	y)	TestGenerateTaskIdu@   generate_task_id()가 중복 없는 ID를 생성하는지 검증c                 p   |j                         }d}||k(  }|st        j                  d|fd||f      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}}y)	u/   타이머 파일이 없을 때 첫 ID는 task-1task-1r`   z%(py0)s == %(py3)stask_idr   r   assert %(py5)sr   N	generate_task_idrj   rk   r   r   r   rl   rm   rn   )rp   rZ   r:   r   rs   @py_assert1@py_format4@py_format6s           r2   test_first_id_is_task_1z*TestGenerateTaskId.test_first_id_is_task_1   si    //1""w(""""w(""""""w"""w"""("""""""rJ   c                    |j                         }|j                         }d}||k(  }|st        j                  d|fd||f      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}}d	}||k(  }|st        j                  d|fd||f      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}}y)u   두 번째 호출은 task-2r   r`   r   id1r   r   r   Ntask-2id2r   )	rp   rZ   r:   r   r   rs   r   r   r   s	            r2   test_second_id_incrementsz,TestGenerateTaskId.test_second_id_increments   s    ++-++-shshsshshshsshrJ   c           	      P   t        d      D cg c]  }|j                          }}t        |      }t        |      }d}||k(  }|sSt	        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                  t              rt	        j                  t              nd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
c c}w )u'   연속 5회 호출 시 모두 다른 ID   r`   )zN%(py6)s
{%(py6)s = %(py0)s(%(py4)s
{%(py4)s = %(py1)s(%(py2)s)
})
} == %(py9)sr   r#   ids)r   rd   py2re   rg   r   assert %(py11)sr   N)ranger   r#   r   rj   rk   r   r   r   rl   rm   rn   )rp   rZ   r:   _r   rr   r   r   @py_assert7@py_format10r   s              r2   test_ids_are_uniquez&TestGenerateTaskId.test_ids_are_unique   s    8=aA1|,,.AAs8!s8}!!}!!!!}!!!!!!s!!!s!!!!!!3!!!3!!!!!!s!!!s!!!8!!!}!!!!!!!!!! Bs   F#c                    |j                         }|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   }	||	v }|st	        j                  d|fd	||	f      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}}	y)uC   생성된 ID가 task-timers.json의 tasks 딕셔너리에 기록됨r   task-timers.jsonAassert %(py4)s
{%(py4)s = %(py2)s
{%(py2)s = %(py0)s.exists
}()
}
timer_filer   r   re   NrO   in)z%(py0)s in %(py3)sr   r   r   r   )r   r"   r   r   rj   r   rl   rm   rn   r%   loads	read_textrk   )rp   rZ   r:   r   r   r   rr   rt   datars   r   r   s               r2   test_id_written_to_timer_filez0TestGenerateTaskId.test_id_written_to_timer_file   s    //1(+==
  " """"""""z"""z""" """"""""""zz*..01w-'w-''''w-''''''w'''w'''-'''''''rJ   c                    |j                         }|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}}y)u$   예약된 ID의 status가 'reserved'r   r   rO   r   reservedr`   rb   rc   rf   rg   N)	r   r%   r   r   rj   rk   rl   rm   rn   )rp   rZ   r:   r   r   r   rq   rr   rs   rt   ru   s              r2   "test_reserved_status_in_timer_filez5TestGenerateTaskId.test_reserved_status_in_timer_file   s    //1(+==
zz*..01G}W%h/=:=/:====/:===/===:=======rJ   c                    |dz  dz  }dddiddiddidi}|j                  t        j                  |      d	       |j                         }d
}||k(  }|st	        j
                  d|fd||f      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}}y)uA   기존 task-timers.json에 task-3.1이 있으면 다음은 task-4r   r   rO   r   	completedrunningtask-1.1task-2.1task-3.1r   r   task-4r`   r   r   r   r   r   N
write_textr%   dumpsr   rj   rk   r   r   r   rl   rm   rn   
rp   rZ   r:   r   existingr   rs   r   r   r   s
             r2   -test_existing_timer_file_increments_correctlyz@TestGenerateTaskId.test_existing_timer_file_increments_correctly   s    (+==
%{3%{3%y1
 	djj2WE//1""w(""""w(""""""w"""w"""("""""""rJ   N)
r   r   r   r   r   r   r   r   r   r   rQ   rJ   r2   r   r      s#    J#
"
(>#rJ   r   c                   l    e Zd ZdZ ej
                  d      d        Zd Zd Zd Z	d Z
d	 Zd
 Zd Zy)TestBuildPromptuG   build_prompt()가 각 팀의 올바른 정보를 포함하는지 검증T)autousec                     ddl m j                  }	 dfd	}|j                  |d|       |j                  t        j
                  j                  d|      d|       y)uM   team_prompts.build_prompt 내부의 하드코딩 경로를 tmp_path로 교체r   Nc                     dz  dz  | dz  }|j                   j                  dd       |j                  |d       j                  j	                  |       }|st        d|        |d d	 }	d
| d|  d|	 d}
d| } d| d}|d   dk(  r$j                  || |t        |      ||
|||	      }nj                  || ||||
|||	      }|dk(  r|j                  |      z  }|dk(  rd|z   }|S |dk(  rd|z   }|S )Nr   rO   .mdTrL   r   r   u   알 수 없는 팀 ID:    zpython3 ... start z --team z	 --desc ""zpython3 ... end z/memory/reports/typedirect
project_idcodingcriticalum   **[CRITICAL] 이 작업은 중요도 critical입니다. 품질 우선으로 신중하게 작업하세요.**

securityue   **[SECURITY] 이 작업은 보안 중요 작업입니다. 보안 최우선으로 작업하세요.**

)
parentrT   r   	TEAM_INFOr!   
ValueError_build_direct_promptr=   _build_glm_prompt_build_verification_section)r   r   	task_desclevelr   chain_id	task_type	task_filer/   
short_desctimer_start	timer_endreport_pathpromptr:   tps                 r2   _patched_buildz8TestBuildPrompt._patch_task_file.<locals>._patched_build   s    !8+g57)3GI""4$"?  W = <<##G,D #:7)!DEE"3BJ.wixy	R\Q]]^_K*7)4I%J&6wisCKF|x'00	N) 1 
 --) . 
 H$"88??
" F  IO  O  M *$ C  FL  LMrJ   _build_team_promptr<   )normalNNr   )rA   team_promptsbuild_promptsetattrr>   rC   r!   )rp   rZ   r:   monkeypatchoriginal_buildr   r   s     `   @r2   _patch_task_filez TestBuildPrompt._patch_task_file   s[     	* dl6	p 	L*>O 	KKOOJ5 	
rJ   c                 x   |j                  ddd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}}y )Nr^      테스트 작업r   r   r      헤르메스r   z%(py1)s in %(py3)sr   rd   r   r   r   	r   rj   rk   rl   r   r   r   rm   rn   rp   rZ   r   rq   rs   r   r   s          r2   #test_dev1_team_contains_leader_namez3TestBuildPrompt.test_dev1_team_contains_leader_name   sv    **;8JJ^f*g'~''''~'''~''''''''''''''''rJ   c                 V   |j                  ddd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 )Nr^   r  r   r   r  u   불칸r   r  r   r	  r   r   u	   이리스u	   아테나u   아르고스r
  r  s          r2   test_dev1_team_contains_membersz/TestBuildPrompt.test_dev1_team_contains_members$  s   **;8JJ^f*g!x6!!!!x6!!!x!!!!!!6!!!6!!!!!!!${f$$$${f$$${$$$$$$f$$$f$$$$$$$${f$$$${f$$${$$$$$$f$$$f$$$$$$$'~''''~'''~''''''''''''''''rJ   c                 x   |j                  ddd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}}y )Nry   r  r   r   r     오딘r   r  r   r	  r   r   r
  r  s          r2   #test_dev2_team_contains_leader_namez3TestBuildPrompt.test_dev2_team_contains_leader_name+  sv    **;8JJ^f*g!x6!!!!x6!!!x!!!!!!6!!!6!!!!!!!rJ   c                 V   |j                  ddd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 )Nry   r  r   r   r  u   토르r   r  r   r	  r   r   u   프레이야u	   미미르u	   헤임달r
  r  s          r2   test_dev2_team_contains_membersz/TestBuildPrompt.test_dev2_team_contains_members/  s   **;8JJ^f*g!x6!!!!x6!!!x!!!!!!6!!!6!!!!!!!'~''''~'''~''''''''''''''''${f$$$${f$$${$$$$$$f$$$f$$$$$$$${f$$$${f$$${$$$$$$f$$$f$$$$$$$rJ   c                 x   |j                  ddd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}}y )Nr}   r  r   r   r  u	   다그다r   r  r   r	  r   r   r
  r  s          r2   #test_dev3_team_contains_leader_namez3TestBuildPrompt.test_dev3_team_contains_leader_name6  sv    **;8JJ^f*g${f$$$${f$$${$$$$$$f$$$f$$$$$$$rJ   c                 x   |j                  ddd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}}y)uO   dev3-team은 다그다 팀장의 direct 타입으로 팀원 목록이 포함됨r}   r  r   r   r  r   r  r   r	  r   r   Nr
  r  s          r2   )test_dev3_team_contains_team_id_in_promptz9TestBuildPrompt.test_dev3_team_contains_team_id_in_prompt:  sv    **;8JJ^f*g${f$$$${f$$${$$$$$$f$$$f$$$$$$$rJ   c                 x   |j                  ddd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}}y )Nr^   r  z	task-99.1r   r  r   r  r   r	  r   r   r
  r  s          r2   test_prompt_contains_task_idz,TestBuildPrompt.test_prompt_contains_task_id?  sv    **;8JK_g*h${f$$$${f$$${$$$$$$f$$$f$$$$$$$rJ   N)r   r   r   r   pytestfixturer  r  r  r  r  r  r  r  rQ   rJ   r2   r   r      sH    QV^^D!E
 "E
N(("%%%
%rJ   r   c                   (    e Zd ZdZd Zd Zd Zd Zy)TestGetDispatchTimeuR   get_dispatch_time()이 올바른 형식의 미래 시간을 반환하는지 검증c                 f   |j                  d      }t        |t              }|sd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dt        j                         v st        j                  t              rt        j                  t              ndt        j                  |      dz  }t        t        j                  |            d }y )N
   5assert %(py4)s
{%(py4)s = %(py0)s(%(py1)s, %(py2)s)
}
isinstanceresultr=   r   rd   r   re   )
get_dispatch_timer!  r=   r   r   rj   r   rl   rm   rn   )rp   rZ   r"  rr   rt   s        r2   test_returns_stringz'TestGetDispatchTime.test_returns_stringL  s    //3&#&&&&&&&&z&&&z&&&&&&&&&&&&&&&&&#&&&#&&&&&&&&&&rJ   c                 R    |j                  d      }t        j                  |d       y )Nr  %Y-%m-%d %H:%M:%S)r$  r   strptime)rp   rZ   r"  s      r2   test_returns_parseable_datetimez3TestGetDispatchTime.test_returns_parseable_datetimeP  s#    //3&"56rJ   c                    ddl m } |j                         }|j                  d      }|j                  |d      }||kD  }|st	        j
                  d|fd||f      dt        j                         v st	        j                  |      rt	        j                  |      nddt        j                         v st	        j                  |      rt	        j                  |      ndd	z  }d
d|iz  }t        t	        j                  |            d }y )Nr   r   <   r'  r   z%(py0)s > %(py2)sparsedbeforer   r   assert %(py4)sre   r   nowr$  r(  rj   rk   r   r   r   rl   rm   rn   	rp   rZ   dtr.  r"  r-  r   @py_format3rt   s	            r2   test_returns_future_timez,TestGetDispatchTime.test_returns_future_timeT  s    +//3V%89vvvrJ   c                    ddl m } |j                         }|j                         }|j                  |d      }||kD  }|st	        j
                  d|fd||f      dt        j                         v st	        j                  |      rt	        j                  |      nddt        j                         v st	        j                  |      rt	        j                  |      nddz  }d	d
|iz  }t        t	        j                  |            d}y)u3   기본 delay_seconds=10일 때 미래 시간 반환r   r   r'  r   r,  r-  r.  r/  r0  re   Nr1  r3  s	            r2   test_default_delayz&TestGetDispatchTime.test_default_delay\  s    +//1V%89vvvrJ   N)r   r   r   r   r%  r)  r6  r8  rQ   rJ   r2   r  r  I  s    \'7rJ   r  c                   (    e Zd ZdZd Zd Zd Zd Zy)TestOrganizationConstantsuB   조직 구조 상수가 올바르게 정의되어 있는지 검증c                 H   t               }d}||kD  }|st        j                  d|fd||f      dt        j                         v st        j
                  |      rt        j                  |      ndt        j                  |      dz  }t        j                  d      dz   d|iz  }t        t        j                  |            d x}}|j                  }t        |      }||k(  }|s7t        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t        j                         v st        j
                  |      rt        j                  |      nddz  }	dd|	iz  }
t        t        j                  |
            d x}x}}y )Nr   r   )z%(py0)s > %(py3)sexpected_team_countr   u7   organization-structure.json에서 팀 수 계산 실패
>assert %(py5)sr   r`   )zO%(py5)s
{%(py5)s = %(py0)s(%(py3)s
{%(py3)s = %(py1)s.TEAM_INFO
})
} == %(py7)sr   rZ   )r   rd   r   r   r   assert %(py9)sr   )r9   rj   rk   r   r   r   rl   r   rm   rn   r   r   )rp   rZ   r<  rs   r   r   r   r   r   @py_format8r   s              r2   test_team_info_has_all_teamsz6TestOrganizationConstants.test_team_info_has_all_teamsn  s,   ;=%&a"Q&aaa"Qaaaaaa"aaa"aaaQaaa(aaaaaaa))As)*A*.AAAAA*.AAAAAAAsAAAsAAAAAA<AAA<AAA)AAA*AAAAAA.AAAA.AAAAAAAArJ   c                 *   |j                   j                         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 )
Nleaderr   r  infor	  r   r   rolemembers)
r   itemsrj   rk   rl   r   r   r   rm   rn   )rp   rZ   r   rC  rq   rs   r   r   s           r2   %test_each_team_info_has_required_keysz?TestOrganizationConstants.test_each_team_info_has_required_keyss  s.   )3399; 	%MGT#8t####8t###8######t###t#######!6T>!!!6T!!!6!!!!!!T!!!T!!!!!!!$9$$$$9$$$9$$$$$$$$$$$$$$$$	%rJ   c           
      B   h d}|j                   }|j                  } |       }t        |      }||k(  }|s_t        j                  d|fd||f      dt        j                         v st        j                  t              rt        j                  t              nddt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      t        j                  |      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}x}x}x}}y )
N>   qcdesigndevopsredteamr`   )z%(py9)s
{%(py9)s = %(py0)s(%(py7)s
{%(py7)s = %(py5)s
{%(py5)s = %(py3)s
{%(py3)s = %(py1)s.CROSS_FUNCTIONAL
}.keys
}()
})
} == %(py11)sr#   rZ   expectedr   r   r   )CROSS_FUNCTIONALrD   r#   rj   rk   r   r   r   rl   rm   rn   )
rp   rZ   rM  rs   r   r   r   r   r   r   s
             r2   (test_cross_functional_has_expected_roleszBTestOrganizationConstants.test_cross_functional_has_expected_rolesy  s    800D055D57Ds78D8HDDDD8HDDDDDDsDDDsDDDDDD<DDD<DDD0DDD5DDD7DDD8DDDDDDHDDDHDDDDDDDDrJ   c                    |j                   j                         D ]P  \  }}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}}S y )	Nnamer   r  rC  r	  r   r   rD  )
rN  rF  rj   rk   rl   r   r   r   rm   rn   )rp   rZ   rR   rC  rq   rs   r   r   s           r2   ,test_cross_functional_each_has_name_and_rolezFTestOrganizationConstants.test_cross_functional_each_has_name_and_role}  s    %66<<> 	"IC!6T>!!!6T!!!6!!!!!!T!!!T!!!!!!!!6T>!!!6T!!!6!!!!!!T!!!T!!!!!!!	"rJ   N)r   r   r   r   r@  rG  rO  rR  rQ   rJ   r2   r:  r:  k  s    LB
%E"rJ   r:  c                   "    e Zd ZdZd Zd Zd Zy)TestInvalidTeamIduT   알 수 없는 팀 ID를 전달했을 때 적절한 에러가 발생하는지 검증c                 ^   t        j                  t              5 }|j                  dddd       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)uH   dispatch.build_prompt()는 잘못된 팀 ID에 대해 sys.exit(1) 호출nonexistent-team   작업r   r   r  Nr6   r`   zG%(py4)s
{%(py4)s = %(py2)s
{%(py2)s = %(py0)s.value
}.code
} == %(py7)sexc_infor   r   re   r   r>  r   r  raises
SystemExitr   valuecoderj   rk   r   r   r   rl   rm   rn   	rp   rZ   rY  r   rr   r   r   r?  r   s	            r2   !test_invalid_team_causes_sys_exitz3TestInvalidTeamId.test_invalid_team_causes_sys_exit  s    ]]:& 	`(%%&8(JV^%_	`~~'~""'a'"a''''"a''''''x'''x'''~'''"'''a'''''''	` 	`s   D##D,c                    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)	uF   TEAM_INFO에 정의되지 않은 팀 키는 포함되지 않아야 함rV  not in)z5%(py1)s not in %(py5)s
{%(py5)s = %(py3)s.TEAM_INFO
}rZ   )rd   r   r   zassert %(py7)sr   N)	r   rj   rk   rl   r   r   r   rm   rn   )rp   rZ   rq   r   rs   r   r?  s          r2   +test_team_info_does_not_contain_invalid_keyz=TestInvalidTeamId.test_team_info_does_not_contain_invalid_key  sw    !?)?)??!)?????!)????!????????????)????????rJ   c                 Z   t        j                  t              5 }|j                  ddd       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) 호출r   rW  r   Nr6   r`   rX  rY  rZ  r>  r   r[  r`  s	            r2   "test_empty_team_id_causes_sys_exitz4TestInvalidTeamId.test_empty_team_id_causes_sys_exit  s    ]]:& 	@(%%b(J?	@~~'~""'a'"a''''"a''''''x'''x'''~'''"'''a'''''''	@ 	@s   D!!D*N)r   r   r   r   ra  re  rg  rQ   rJ   r2   rT  rT    s    ^(@(rJ   rT  c                   .    e Zd ZdZd Zd Zd Zd Zd Zy)TestGenerateTaskIdErrorHandlingu4   generate_task_id() 에러 핸들링 분기 테스트c                     |dz  dz  }|j                  dd       t        j                  t        d      5  |j	                          ddd       y# 1 sw Y   yxY w)	uq   task-timers.json이 깨진 JSON일 때 RuntimeError 발생 (파일 손상 = 하드코딩 기본값 사용 금지)r   r   INVALID JSON{{{r   r   u   손상matchN)r   r  r\  RuntimeErrorr   rp   rZ   r:   r   s       r2   (test_corrupted_json_raises_runtime_errorzHTestGenerateTaskIdErrorHandling.test_corrupted_json_raises_runtime_error  sU    (+==
/'B]]<x8 	,))+	, 	, 	,s   AAc                    |dz  dz  }dddiddidi}|j                  t        j                  |      d       |j                         }d	}||k(  }|st	        j
                  d
|fd||f      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}}y)uG   숫자로 변환 불가능한 task ID가 있으면 skip (lines 110-111)r   r   rO   r   r   )ztask-abc.defr   r   r   task-3r`   r   r   r   r   r   Nr   r   s
             r2    test_unparseable_task_id_skippedz@TestGenerateTaskIdErrorHandling.test_unparseable_task_id_skipped  s    (+==
x.ET\^iSjkldjj2WE//1""w(""""w(""""""w"""w"""("""""""rJ   c                 R   |dz  dz  }|j                  dd       ddl}|j                  dgfd}t        d	|
      5  |j	                         }ddd       j
                  }d} ||      }	|	sddt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      t        j                  |	      dz  }
t        t        j                  |
            dx}x}}	y# 1 sw Y   xY w)u?   task-timers.json 재읽기 실패 시 초기화 (lines 121-123)r   r   z+{"tasks": {"task-1.1": {"status": "done"}}}r   r   r   Nc                      | rt        | d         nd}t        |       dkD  rt        | d         }nt        |j                  dd            }d|v r(d|v r$d|vr dxx   dz  cc<   d   dk(  rt        d	       | i |S )
Nr   r   r6   moderr   w   u   모킹된 읽기 실패r=   r   r!   OSError)argskwargsf_pathrv  
call_countoriginal_opens       r2   mock_open_fnzTTestGenerateTaskIdErrorHandling.test_timer_file_reread_failure.<locals>.mock_open_fn  s    %)Sa\rF4y1}47|6::fc23!V+t41"a=A%!";<< $1&11rJ   builtins.openside_effecttask-Lassert %(py6)s
{%(py6)s = %(py2)s
{%(py2)s = %(py0)s.startswith
}(%(py4)s)
}r   r   r   re   rg   )r   builtinsr$   r   r   r'   r   r   rj   r   rl   rm   rn   )rp   rZ   r:   r   r  r  r   r   rr   r   ru   r  r  s              @@r2   test_timer_file_reread_failurez>TestGenerateTaskIdErrorHandling.test_timer_file_reread_failure  s    (+==
KV]^ S
	2 ?= 	6"335G	6 !!*'*!'********w***w***!***'**********	6 	6s   DD&c                 6   |dz  dz  }|j                  dd       |j                         }d}||k(  }|st        j                  d|fd||f      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}}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}
}y)u@   timer_data에 'tasks' 키가 없을 때 자동 추가 (line 127)r   r   {}r   r   r   r`   r   r   r   r   r   NrO   r   r  r   r	  )r   r   rj   rk   r   r   r   rl   rm   rn   r%   r   r   )rp   rZ   r:   r   r   rs   r   r   r   r   rq   s              r2   !test_timer_data_without_tasks_keyzATestGenerateTaskIdErrorHandling.test_timer_data_without_tasks_key  s    (+==
dW5//1""w(""""w(""""""w"""w"""("""""""zz*..01w$w$w$$rJ   c                   
 ddl }|j                  

fd}t        d|      5  |j                         }ddd       j                  }d} ||      }|sddt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      t        j                  |      d	z  }	t        t        j                  |	            dx}x}}y# 1 sw Y   xY w)
uA   task-timers.json 쓰기 실패해도 ID는 반환 (lines 136-137)r   Nc                      | rt        | d         nd}t        |       dkD  rt        | d         }nt        |j                  dd            }d|v rd|v rt        d       | i |S )	Nr   r   r6   rv  rw  r   rx  u   쓰기 실패 모킹rz  )r|  r}  r~  rv  r  s       r2   mock_open_writez\TestGenerateTaskIdErrorHandling.test_write_failure_still_returns_id.<locals>.mock_open_write  sk    %)Sa\rF4y1}47|6::fc23!V+t455 $1&11rJ   r  r  r  r  r   r  )r  r$   r   r   r'   r   r   rj   r   rl   rm   rn   )rp   rZ   r:   r  r  r   r   rr   r   ru   r  s             @r2   #test_write_failure_still_returns_idzCTestGenerateTaskIdErrorHandling.test_write_failure_still_returns_id  s     	2 ?@ 	6"335G	6!!*'*!'********w***w***!***'**********	6 	6s   C==DN)	r   r   r   r   rp  rs  r  r  r  rQ   rJ   r2   ri  ri    s    >,#+8+rJ   ri  c                   T    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d Zd Zy)TestDispatchFunctionu+   dispatch() 함수 테스트 (lines 198-278)Nc                 v    t               }||_        ||nt        j                  ddi      |_        ||_        |S )u   subprocess mock 헬퍼r   okr   
returncoder%   r   stdoutstderrrp   r  r  r  mock_results        r2   _make_mock_subprocessz*TestDispatchFunction._make_mock_subprocess  s=    k!+'-'9Vtzz8UYJZ?[#rJ   c                    | j                  dt        j                  ddd            }t        j                  |d      5 }t        j                  |dddd	      5  ||j
                  _        |j                  d
d      }d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}||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# 1 sw Y   xY w)u.   dispatch() 성공 시 status=dispatched 반환r   r  z	sched-123)r   id
subprocessBOT_KEYSkey1anu-keyr_   anur^   r  Nr   
dispatchedr`   rb   rc   rf   rg   r/   r   r   r  r"  r	  r   r   )r  r%   r   r   objectrunreturn_valuer<   rj   rk   rl   rm   rn   r   r   r   )rp   rZ   r:   r  mock_subr"  rq   rr   rs   rt   ru   r   r   s                r2   test_dispatch_successz*TestDispatchFunction.test_dispatch_success  s   00DJJ$Va?b4cd LL|4	L8@LLzF93UV	L )4HLL%!**;8JKF	L 	L h/</<////<//////<///////f~,,~,,,,~,,,~,,,,,,,,,,"yF""""yF"""y""""""F"""F"""""""	L 	L 	L 	Ls#   I$H4>I4H>	9IIc                 &   | j                  ddd      }t        j                  |d      5 }t        j                  |dddd      5  ||j                  _        |j                  d	d
      }d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   }||v }|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# 1 sw Y   xY w)u)   dispatch() 실패 시 status=error 반환r6   r   zcommand not foundr  r  r  r  r  r^   r  Nr   errorr`   rb   rc   rf   rg   messager   z%(py1)s in %(py4)sr  r   r  r  r  r<   rj   rk   rl   rm   rn   rp   rZ   r:   r  r  r"  rq   rr   rs   rt   ru   s              r2   test_dispatch_failurez*TestDispatchFunction.test_dispatch_failure  s;   00B8KL LL|4	L8@LLzF93UV	L )4HLL%!**;8JKF	L 	L h*7*7****7******7*******"7fY&77"&77777"&7777"777&77777777	L 	L 	L 	Ls#   F$E9)F9F	>FFc                    t        j                  |d      5 }t        j                  |dddd      5  t        ddd	      |j                  _        |j                  d
dd      }d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   }||v }|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# 1 sw Y   xY w)u3   존재하지 않는 project_id일 때 에러 반환r  r  r  r  r  r   r  r   r  r  r  r^   rW  znonexistent-projr   Nr   r  r`   rb   rc   rf   rg      존재하지 않r  r   r  r   r  r   r  r  r<   rj   rk   rl   rm   rn   
rp   rZ   r:   r  r"  rq   rr   rs   rt   ru   s
             r2   !test_dispatch_nonexistent_projectz6TestDispatchFunction.test_dispatch_nonexistent_project!  s5    LL|4	a8@LLzF93UV	a )2QtTV(WHLL%!**;M_*`F	a 	a h*7*7****7******7*******!6VI%66!%66666!%6666!666%66666666	a 	a 	a 	as"   F2E4$F4E>	9FFc                    t        j                  |d      5 }t        j                  |di       5  t        ddd      |j                  _        |j                  dd      }d	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   }||v }|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# 1 sw Y   xY w)u'   BOT_KEYS에 봇 키가 없으면 에러r  r  r   r  r   r  r^   rW  Nr   r  r`   rb   rc   rf   rg   u   할당된 봇이 없습니다r  r   r  r  r  s
             r2   test_dispatch_no_bot_keyz-TestDispatchFunction.test_dispatch_no_bot_key,  s*    LL|4	B8@LLz26	B )2QtTV(WHLL%!**;AF	B 	B h*7*7****7******7*******.C&2CC.2CCCCC.2CCCC.CCC2CCCCCCCC	B 	B 	B 	Bs"   E<0E/E</E9	4E<<Fc                    t        j                  |d      5 }t        j                  |dddd      5  t        ddd	      |j                  _        |j                  d
d      }d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   }||v }|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# 1 sw Y   xY w)uB   BOT_KEYS[bot_id]가 None이면 _cleanup_task 후 error dict 반환r  r  Nr  r  r   r  r   r  r^   rW  r   r  r`   rb   rc   rf   rg   '   봇 키가 설정되지 않았습니다r  r   r  r  r  s
             r2    test_dispatch_bot_key_none_exitsz5TestDispatchFunction.test_dispatch_bot_key_none_exits7  s/    LL|4	B8@LLzD3ST	B )2QtTV(WHLL%!**;AF	B 	B h*7*7****7******7*******8MF9<MM8<MMMMM8<MMMM8MMM<MMMMMMMM	B 	B 	B 	Bs"   E?0E2"E?2E<	7E??F	c                 $   | j                  dd      }t        j                  |d      5 }t        j                  |dddd      5  ||j                  _        |j                  dd	      }d
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   }||v }|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# 1 sw Y   xY w)uD   subprocess 성공했지만 stdout이 유효한 JSON이 아닌 경우r   znot valid json outputr  r  r  r  r  r^   	   테스트Nr   r  r`   rb   rc   rf   rg   rawcron_responser   r  r  r  s              r2   )test_dispatch_json_decode_error_in_stdoutz>TestDispatchFunction.test_dispatch_json_decode_error_in_stdoutB  s5   004KL LL|4	E8@LLzF93UV	E )4HLL%!**;DF	E 	E h/</<////<//////<//////////u/////u////u///////////	E 	E 	E 	Es#   F$E8(F8F	=FFc                    | j                  dt        j                  ddi            }t        j                  |d      5 }t        j                  |dddd      5  ||j
                  _        |j                  d	d
      }ddd       ddd       d   }|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}}	y# 1 sw Y   xY w# 1 sw Y   xY w)uA   dispatch()가 memory/tasks/<task_id>.md 파일을 생성하는지r   r   r  r  r  r  r  r  r^   u   파일 생성 테스트Nr   r   rO   r   r   r   r   )r  r%   r   r   r  r  r  r<   r"   r   r   rj   r   rl   rm   rn   )rp   rZ   r:   r  r  r"  r   r   r   rr   rt   s              r2   test_dispatch_creates_task_filez4TestDispatchFunction.test_dispatch_creates_task_fileP  s!   00DJJ$?O4PQ LL|4	S8@LLzF93UV	S )4HLL%!**;8QRF	S 	S #x''1wisOC	!!!!!!!!!y!!!y!!!!!!!!!!!!!	S 	S 	S 	Ss#   E$E=EE	EEc                    |dz  dz  j                  dd       | j                  dt        j                  ddi            }t	        j
                  |d      5 }t	        j
                  |d	d
dd      5  ||j                  _        |j                  ddd      }d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# 1 sw Y   xY w# 1 sw Y   xY w)u.   존재하는 project_id일 때 정상 dispatchprojectsmyprojTrL   r   r   r  r  r  r  r  r  r^      프로젝트 작업r   Nr  r`   rb   rc   rf   rg   )rT   r  r%   r   r   r  r  r  r<   rj   rk   rl   rm   rn   r  s              r2   test_dispatch_existing_projectz3TestDispatchFunction.test_dispatch_existing_project_  s   	J		)000M00DJJ$?O4PQ LL|4	d8@LLzF93UV	d )4HLL%!**;8MZb*cF	d 	d h/</<////<//////<///////	d 	d 	d 	ds$   D52&D)D5)D2	.D55D>c                 N   | j                  dt        j                  ddi            }t        j                  |d      5 }t        j                  |dddd      5  ||j
                  _        |j                  d	d
      }d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   }||v }|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# 1 sw Y   xY w)u9   dispatch() 결과에 팀장 이름이 포함되어야 함r   r   r  r  r  key2r  rz   r  ry   rW  Nr  r`   rb   rc   rf   rg   r  leadr   r  r  r%   r   r   r  r  r  r<   rj   rk   rl   rm   rn   r  s              r2   test_dispatch_returns_lead_namez4TestDispatchFunction.test_dispatch_returns_lead_namem  s>   00DJJ$?O4PQ LL|4	B8@LLzF93UV	B )4HLL%!**;AF	B 	B h/</<////<//////<///////)6&>)x>))))x>)))x)))>)))))))	B 	B 	B 	Bs#   F$F=FF	FF$c                 P   | j                  dt        j                  ddi            }t        j                  |d      5 }t        j                  |dddd      5  ||j
                  _        |j                  d	d
d      }d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# 1 sw Y   xY w# 1 sw Y   xY w)u1   dispatch()에 level 인자가 결과에 반영됨r   r   r  r  r  r  r  r  r^      중요 작업r   r  Nr   r`   rb   rc   rf   rg   r  r  s              r2   test_dispatch_level_propagatedz3TestDispatchFunction.test_dispatch_level_propagated{  s    00DJJ$?O4PQ LL|4	[8@LLzF93UV	[ )4HLL%!**;z*ZF	[ 	[ g,*,*,,,,*,,,,,,*,,,,,,,	[ 	[ 	[ 	[s#   D&D?DD	DD%r   Nr   )r   r   r   r   r  r  r  r  r  r  r  r  r  r  r  rQ   rJ   r2   r  r    s>    5#8	7	D	N0"0*-rJ   r  c                   4    e Zd ZdZd Zd Zd Zd Zd Zd Z	y)	TestMainCLIu+   main() CLI 함수 테스트 (lines 282-299)c                 .   t               }d|_        t        j                  ddi      |_        |j                  t        dg d       t        j                  |d      5 }t        j                  |ddd	d
      5  ||j                  _
        |j                          ddd       ddd       |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}}	y# 1 sw Y   xY w# 1 sw Y   xY w)u0   main()이 dispatch() 결과를 JSON으로 출력r   r   r  argv)dispatch.py--teamr^   --tasku   CLI 테스트r  r  r  r  r  Nr   r  outputr	  r   r   r   r  r%   r   r  r   r>   r   r  r  r  main
readouterrr   outrj   rk   rl   r   r   r   rm   rn   rp   rZ   r  capsysr  r  capturedr  rq   rs   r   r   s               r2   $test_main_dispatches_and_prints_jsonz0TestMainCLI.test_main_dispatches_and_prints_json  s!   k!"!ZZ4(89C)jk LL|4	 8@LLzF93UV	  )4HLL%	  	  $$&HLL)!x6!!!!x6!!!x!!!!!!6!!!6!!!!!!!	  	  	  	 $   F8"E?F?F	FFc                    t               }d|_        t        j                  ddi      |_        |j                  t        dg d       t        j                  |d      5 }t        j                  |ddd	d
      5  ||j                  _
        |j                          ddd       ddd       |j                         }t        j                  |j                        }g }|j                  }	d}
 |	|
      }d}||k(  }|}|sd}||v }|}|st!        j"                  d|fd||f      dt%        j&                         v st!        j(                  |      rt!        j*                  |      ndt!        j*                  |	      t!        j*                  |
      t!        j*                  |      t!        j*                  |      dz  }dd|iz  }|j-                  |       |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  }|j-                  |       t!        j.                  |d      i z  }dd|iz  }t1        t!        j2                  |            dx}x}x}	x}
x}x}x}x}}y# 1 sw Y   xY w# 1 sw Y   xY w)u!   main()에 --level critical 전달r   r   r  r  )r  r  ry   r  r  z--levelr   r  r  r  r  r  Nr   r   r`   )zJ%(py8)s
{%(py8)s = %(py4)s
{%(py4)s = %(py2)s.get
}(%(py6)s)
} == %(py11)sr  )r   re   rg   r   r   z%(py13)sr   r   )z%(py16)s in %(py18)s)py16py18z%(py20)spy20r6   zassert %(py23)spy23)r   r  r%   r   r  r   r>   r   r  r  r  r  r  r   r  r!   rj   rk   r   r   r   rl   append_format_booloprm   rn   )rp   rZ   r  r  r  r  r  r  r   rr   r   r   r   @py_assert9rq   @py_assert15@py_assert17r   r   @py_format19@py_format21@py_format22@py_format24s                          r2   test_main_with_level_criticalz)TestMainCLI.test_main_with_level_critical  s   k!"!ZZ4(89q	

 LL|4	 8@LLzF93UV	  )4HLL%	  	  $$&HLL)FvzzF'Fz'"FjF"j0FHFH4FFFFF"jFFFFFFvFFFvFFFzFFF'FFF"FFFjFFFFFFFHFFFHFFFFFFFFFFFFFFFFFFFFFFFF	  	  	  	 s$   J;8"J.J;.J8	3J;;Kc                 `   |dz  dz  j                  dd       t               }d|_        t        j                  ddi      |_        |j                  t        dg d	       t        j                  |d
      5 }t        j                  |dddd      5  ||j                  _        |j                          ddd       ddd       |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}	}
y# 1 sw Y   xY w# 1 sw Y   xY w)u%   main()에 --session, --project 전달r  testprojTrL   r   r   r  r  )	r  r  r^   r  r  z	--sessionzsess-abcz	--projectr  r  r  r  r  r  Nr   r  r  r	  r   r   )rT   r   r  r%   r   r  r   r>   r   r  r  r  r  r  r   r  rj   rk   rl   r   r   r   rm   rn   )rp   rZ   r  r  r:   r  r  r  r  rq   rs   r   r   s                r2   "test_main_with_session_and_projectz.TestMainCLI.test_main_with_session_and_project  sF    
J		+224$2Ok!"!ZZ4(89
	
" LL|4	 8@LLzF93UV	  )4HLL%	  	  $$&HLL)!x6!!!!x6!!!x!!!!!!6!!!6!!!!!!!	  	  	  	 s$   6F$"F3F$F!	F$$F-c                 .   t               }d|_        t        j                  ddi      |_        |j                  t        dg d       t        j                  |d      5 }t        j                  |ddd	d
      5  ||j                  _
        |j                          ddd       ddd       |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}}	y# 1 sw Y   xY w# 1 sw Y   xY w)u   main()에 dev3-team 전달r   r   r  r  )r  r  r}   r  u
   GLM 작업r  r  key3r  r~   r  Nr   r  r  r	  r   r   r  r  s               r2   test_main_dev3_teamzTestMainCLI.test_main_dev3_team  s!   k!"!ZZ4(89C)gh LL|4	 8@LLzF93UV	  )4HLL%	  	  $$&HLL)!x6!!!!x6!!!x!!!!!!6!!!6!!!!!!!	  	  	  	 r  c           
         |dz  dz  }|j                  t        j                  di i      d       t               }d|_        t        j                  ddi      |_        |j                  t        d	g d
       t        j                  |d      5 }t        j                  |dddddd      5  ||j                  _        |j                          ddd       ddd       |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}
}y# 1 sw Y   xY w# 1 sw Y   xY w)u!   main()에 --team marketing 전달r   r   rO   r   r   r   r   r  r  )r  r  	marketingr     마케팅 작업r  r  r  r  r  r  r_   rz   r~   r  Nr   r  r  r	  r   r   r   r%   r   r   r  r  r   r>   r   r  r  r  r  r  r   r  rj   rk   rl   r   r   r   rm   rn   rp   rZ   r  r  r:   r   r  r  r  r  rq   rs   r   r   s                 r2   test_main_marketing_teamz$TestMainCLI.test_main_marketing_team  sZ   (+==
djj'27'Jk!"!ZZ4(89C)mn LL|4		 8@LL	R		  )4HLL%		  		  $$&HLL)!x6!!!!x6!!!x!!!!!!6!!!6!!!!!!!		  		  		  		 $   F=*"F1F=1F:	6F==Gc           
         |dz  dz  }|j                  t        j                  di i      d       t               }d|_        t        j                  ddi      |_        |j                  t        d	g d
       t        j                  |d      5 }t        j                  |dddddd      5  ||j                  _        |j                          ddd       ddd       |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}
}y# 1 sw Y   xY w# 1 sw Y   xY w)u"   main()에 --team consulting 전달r   r   rO   r   r   r   r   r  r  )r  r  
consultingr  u   컨설팅 작업r  r  r  r  r  r  r  Nr   r  r  r	  r   r   r  r  s                 r2   test_main_consulting_teamz%TestMainCLI.test_main_consulting_team
  sZ   (+==
djj'27'Jk!"!ZZ4(89C)no LL|4		 8@LL	R		  )4HLL%		  		  $$&HLL)!x6!!!!x6!!!x!!!!!!6!!!6!!!!!!!		  		  		  		 r   N)
r   r   r   r   r  r  r  r  r  r  rQ   rJ   r2   r  r    s%    5"&G*""H"&"4"rJ   r  c                   .    e Zd ZdZd Zd Zd Zd Zd Zy)TestSubprocessTimeoutu)   subprocess.run() timeout 처리 테스트c                 6   ddl fd}t        j                  |d      5 }t        j                  |dddd      5  ||j                  _        j
                  |_        |j                  d	d
      }d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   }||v }|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# 1 sw Y   xY w)uC   cokacdir 호출에서 TimeoutExpired 발생 시 status=error 반환r   Nc                      | r| d   n|j                  dg       }t        |t              r|r|d   dk(  rj                  |d      t	               }d|_        d|_        d|_        |S Nr   r|  cokacdirr+  r   r!   r!  rB   TimeoutExpiredr   r  r  r  r|  r}  cmdr  real_subprocesss       r2   mock_run_side_effectzRTestSubprocessTimeout.test_dispatch_cokacdir_timeout.<locals>.mock_run_side_effect1  sj    !$q'vzz&"'=C#t$Q:1E%44S"==#+K%&K"!#K!#KrJ   r  r  r  r  r  r^   u   타임아웃 테스트 작업r   r  r`   rb   rc   rf   rg   u   타임아웃r  r   r  r  r   r  r  r  r  r<   rj   rk   rl   rm   rn   )rp   rZ   r:   r  r  r"  rq   rr   rs   rt   ru   r  s              @r2   test_dispatch_cokacdir_timeoutz4TestSubprocessTimeout.test_dispatch_cokacdir_timeout-  s:   ,
	 LL|4	Y8@LLzF93UV	Y (<HLL$&5&D&DH#!**;8WXF	Y 	Y h*7*7****7******7*******2	!22~!22222~!2222~222!22222222	Y 	Y 	Y 	Ys"   F5F1FF	FFc                    ddl g fd}fd}t        j                  |d      5 }t        j                  |dddd	      5  t        j                  |d
|      5  ||j                  _        j
                  |_        |j                  dd       ddd       ddd       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dt        j                         v st        j                        rt        j                        ndt        j                  |      t        j                  |      dz  }	dd|	iz  }
t        t        j                  |
            dx}x}}y# 1 sw Y   +xY w# 1 sw Y   0xY w# 1 sw Y   5xY w)u6   cokacdir TimeoutExpired 시 _cleanup_task가 호출됨r   Nc                 (    j                  |        y Nr  r   cleanup_calleds    r2   mock_cleanupzXTestSubprocessTimeout.test_dispatch_cokacdir_timeout_calls_cleanup.<locals>.mock_cleanupN      !!'*rJ   c                      | r| d   n|j                  dg       }t        |t              r|r|d   dk(  rj                  |d      t	               }d|_        d|_        d|_        |S r  r
  r  s       r2   r  z`TestSubprocessTimeout.test_dispatch_cokacdir_timeout_calls_cleanup.<locals>.mock_run_side_effectQ  sj    !$q'vzz&"'=C#t$Q:1E%44S"==#+K%&K"!#K!#KrJ   r  r  r  r  r  _cleanup_taskr  r^   u   cleanup 확인 테스트r6   r`   z0%(py3)s
{%(py3)s = %(py0)s(%(py1)s)
} == %(py6)sr   r  r   assert %(py8)sr   )r  r   r  r  r  r  r<   r   rj   rk   r   r   r   rl   rm   rn   )rp   rZ   r:   r  r  r  rs   r   r   ru   r   r  r  s              @@r2   ,test_dispatch_cokacdir_timeout_calls_cleanupzBTestSubprocessTimeout.test_dispatch_cokacdir_timeout_calls_cleanupH  sY   ,	+	 LL|4	K8@LLzF93UV	K LLLQ	K
 (<HLL$&5&D&DH#!!+/IJ	K 	K 	K >"'a'"a''''"a''''''s'''s''''''>'''>'''"'''a'''''''	K 	K 	K 	K 	K 	Ks;   GG5F;GG;G GG	GGc                    ddl }t        t        j                  j	                  dd            }|dz  dz  }|j
                  j                  d|      }g }d}||u}|}	|r|j                  }
d}|
|u}|}	|	slt        j                  d|fd	||f      d
t        j                         v st        j                  |      rt        j                  |      nd
t        j                  |      dz  }dd|iz  }|j                  |       |rt        j                  dfd
f      d
t        j                         v st        j                  |      rt        j                  |      nd
t        j                  |
      t        j                  |      dz  }dd|iz  }|j                  |       t        j                  |d      i z  }dd|iz  }t!        t        j"                  |            dx}	x}x}x}x}
x}}|j
                  j%                  |      }|j                  j'                  |       |dz  dz  }|j)                  dd       t+        j,                  |dt/        |            5  t+        j,                  |dddddd      5  t+        j,                  |dddi      5  t+        j,                  |d d      5  t+        j,                  t0        d!dd"g      5  t+        j2                  t        j                  d#d$d%      5  |j5                          ddd       ddd       ddd       ddd       ddd       ddd       d&}||z  }|j6                  } |       }| }|sd'd(t        j                         v st        j                  |      rt        j                  |      nd(t        j                  |      t        j                  |      t        j                  |      d)z  }t!        t        j"                  |            dx}x}x}x}}y# 1 sw Y   xY w# 1 sw Y   xY w# 1 sw Y   xY w# 1 sw Y   xY w# 1 sw Y   xY w# 1 sw Y   xY w)*uP   notify-completion.py의 Telegram 전송 실패 시 sys.exit 없이 정상 반환r   Nr   r   scriptsznotify-completion.pynotify_completionis not)z%(py2)s is not %(py5)sspec)r   r   z%(py7)sr   )z5%(py11)s
{%(py11)s = %(py9)s.loader
} is not %(py14)s)r   r   py14%(py16)sr  assert %(py19)spy19r   eventsTrL   check_chain_statusFz
chain-test)in_chainis_lastr   next_task_idr  dispatch_next_phaseactionnonesend_telegram_notificationr  
task-999.1ztest-keyz
test-token)COKACDIR_KEY_ANUANU_BOT_TOKENztask-999.1.done.clearzQassert not %(py7)s
{%(py7)s = %(py5)s
{%(py5)s = (%(py0)s / %(py2)s).exists
}()
}
events_dir)r   r   r   r   )importlib.utilr   r   r    r!   utilspec_from_file_locationloaderrj   rk   r   r   r   rl   r  r  rm   rn   module_from_specexec_modulerT   r   r  r=   r>   dictr  r"   )rp   r:   	importlibr*   notify_scriptr$  r   r   rr   rq   r   @py_assert13@py_assert12r   r?  @py_format15@py_format17@py_format18@py_format20rY   r6  r   r   r   s                           r2   +test_notify_completion_send_failure_no_exitzATestSubprocessTimeout.test_notify_completion_send_failure_no_exitf  s   (8:OPQ	!I-0FF~~556I=Y;4;t4;DKK;t;Kt$;;;;;t4;;;;;;t;;;t;;;4;;;;;;;Kt;;;;;;D;;;D;;;K;;;t;;;;;;;;;;;;;;nn--d3$(83
5 LL.H>	LL$*.5llpq	 LL%&/	 LL:N	 LLf'=|&LM	 JJrzz
Ua#bc	" HHJ#	 	 	 	 	 	( "9BJ!88B8@@B@BBBBBBBBBBBJBBBJBBB!8BBB@BBBBBBBBBBB)	 	 	 	 	 	 	 	 	 	 	 	s    Q>P7P*2P	(P8P	PP	P*!P7)QP	PPP	P'"P**P4/P77Q	<QQc           	         ddl }g fd}t        j                  |d      5 }t        j                  |dddd      5  ||j                  _        |j
                  |_        |j                  d	d
       ddd       ddd       D cg c]*  }t        |d   t              s|d   s|d   d   dk(  s)|, }}|D ]  }|d   }	|	j                  }
d} |
|      }d}||k(  }|st        j                  d|fd||f      t        j                  |	      t        j                  |
      t        j                  |      t        j                  |      t        j                  |      dz  }t        j                  d|       dz   d|iz  }t        t        j                  |            dx}	x}
x}x}x}} y# 1 sw Y   @xY w# 1 sw Y   ExY wc c}w )u9   로컬 python3 timer 호출은 timeout=30으로 호출됨r   Nc                      j                  | r| d   ng |d       t               }d|_        t        j                  ddi      |_        d|_        |S Nr   )r|  r}  r   r  r   r  r   r  r%   r   r  r  r|  r}  r  	run_callss      r2   mock_run_capturezVTestSubprocessTimeout.test_dispatch_timer_cmd_has_timeout_30.<locals>.mock_run_capture  Q    d1g2PQ#+K%&K"!%Xt,<!=K!#KrJ   r  r  r  r  r  r^   u   타이머 timeout 확인r|  python3r}  timeout   r`   zJ%(py7)s
{%(py7)s = %(py3)s
{%(py3)s = %(py1)s.get
}(%(py5)s)
} == %(py10)srd   r   r   r   py10u(   python3 호출에 timeout=30 미설정: 
>assert %(py12)spy12)r  r   r  r  r  r  r<   r!  rB   r!   rj   rk   rl   r   rm   rn   )rp   rZ   r:   r  rM  r  cpython3_callscallrq   rs   r   r   r  r   @py_format11@py_format13rL  s                    @r2   &test_dispatch_timer_cmd_has_timeout_30z<TestSubprocessTimeout.test_dispatch_timer_cmd_has_timeout_30  s   ,		 LL|4	K8@LLzF93UV	K (8HLL$&5&D&DH#!!+/IJ	K 	K !
Jqy$$?AfIRSTZR[\]R^bkRkA
 
 " 	jD>i>%%iii%i0iBi0B6iii0Biii>iii%iiiiiii0iiiBiii:bcgbh8iiiiiiii	j	K 	K 	K 	K
s:   F>5F13F>G G&G2G1F;	6F>>Gc           	          ddl }g fd}t        j                  |d      5 }t        j                  |dddd      5  ||j                  _        |j
                  |_        |j                  d	d
       ddd       ddd       D cg c]C  }t        |d   t              r.|d   r)|d   d   dk(  rt        |d         dk\  r|d   d   dk7  r|E }}t        |      }d}	||	k\  }
|
st        j                  d|
fd||	f      dt        j                         v st        j                  t              rt        j                  t              nddt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |	      dz  }t        j                   d      dz   d|iz  }t#        t        j$                  |            dx}x}
}	|D ]  }|d   }|j&                  }d}
 ||
      }d}||k(  }|st        j                  d|fd||f      t        j                  |      t        j                  |      t        j                  |
      t        j                  |      t        j                  |      dz  }t        j                   d|       d z   d!|iz  }t#        t        j$                  |            dx}x}x}
x}x}} y# 1 sw Y   xY w# 1 sw Y   xY wc c}w )"u4   외부 cokacdir 호출은 timeout=60으로 호출됨r   Nc                      j                  | r| d   ng |d       t               }d|_        t        j                  ddi      |_        d|_        |S rI  rJ  rK  s      r2   rM  zYTestSubprocessTimeout.test_dispatch_cokacdir_cmd_has_timeout_60.<locals>.mock_run_capture  rN  rJ   r  r  r  r  r  r^   u   cokacdir timeout 확인r|  r	     ry  .r6   >=z0%(py3)s
{%(py3)s = %(py0)s(%(py1)s)
} >= %(py6)sr   main_cokacdir_callsr   u    메인 cokacdir 호출이 없음r   r   r}  rP  r+  r`   rR  rS  u)   cokacdir 호출에 timeout=60 미설정: rU  rV  )r  r   r  r  r  r  r<   r!  rB   r   rj   rk   r   r   r   rl   r   rm   rn   r!   )rp   rZ   r:   r  rM  r  rW  rd  rs   r   r   ru   r   rY  rq   r   r  r   rZ  r[  rL  s                       @r2   )test_dispatch_cokacdir_cmd_has_timeout_60z?TestSubprocessTimeout.test_dispatch_cokacdir_cmd_has_timeout_60  sR   ,		 LL|4	J8@LLzF93UV	J (8HLL$&5&D&DH#!!+/HI	J 	J !
!F)T*qyQvYq\Z=WAfI!#&	!(; 
 

 &'P1P'1,PPP'1PPPPPPsPPPsPPPPPP&PPP&PPP'PPP1PPP.PPPPPPPP' 	kD>j>%%jij%i0jBj0B6jjj0Bjjj>jjj%jjjijjj0jjjBjjj:cdhci8jjjjjjjj	k!	J 	J 	J 	J
s)   K>5K13K>AL1K;	6K>>LN)	r   r   r   r   r  r  rF  r\  re  rQ   rJ   r2   r  r  *  s"    336(<$CLj:krJ   r  c                   @    e Zd ZdZd Zd Zd Zd Zd Zd Z	d Z
d	 Zy
)TestFindAvailableBotu8   _find_available_bot() 가용 봇 자동 선택 테스트c                    |dz  dz  }|j                  t        j                  di i      d       |j                         }d}||k(  }|st	        j
                  d|fd||f      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}}y)uA   모든 봇이 비어있으면 우선순위 최고인 bot-b 반환r   r   rO   r   r   bot-br`   r   r"  r   r   r   Nr   r%   r   _find_available_botrj   rk   r   r   r   rl   rm   rn   	rp   rZ   r:   r   r"  rs   r   r   r   s	            r2    test_all_bots_free_returns_bot_bz5TestFindAvailableBot.test_all_bots_free_returns_bot_b  s    (+==
djj'27'J113  v    v      v   v          rJ   c                    |dz  dz  }dddddii}|j                  t        j                  |      d	       |j                         }d
}||k(  }|st	        j
                  d|fd||f      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}}y)u<   dev1-team이 running이면 bot-b 사용 중 → bot-c 반환r   r   rO   r   r^   r   r   r   r   r   bot-cr`   r   r"  r   r   r   Nrj  
rp   rZ   r:   r   r   r"  rs   r   r   r   s
             r2   test_dev1_running_returns_bot_cz4TestFindAvailableBot.test_dev1_running_returns_bot_c  s    (+==
*+&STUdjj.A113  v    v      v   v          rJ   c                    |dz  dz  }ddddddddi}|j                  t        j                  |      d	
       |j                         }d}||k(  }|st	        j
                  d|fd||f      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}}y)u*   dev1, dev2 모두 running → bot-d 반환r   r   rO   r^   r   ro  ry   r   r   r   r   bot-dr`   r   r"  r   r   r   Nrj  rq  s
             r2   $test_dev1_dev2_running_returns_bot_dz9TestFindAvailableBot.test_dev1_dev2_running_returns_bot_d  s    (+==
(3yI(3yI
 	djj.A113  v    v      v   v          rJ   c                 *   |dz  dz  }ddddddddddd	ddd
ddddddddddddi}|j                  t        j                  |      d       t        j                  t
        d      5  |j                          ddd       y# 1 sw Y   yxY w)u2   모든 봇이 사용 중이면 RuntimeError 발생r   r   rO   r^   r   ro  ry   r}   	dev4-team	dev5-team	dev6-team	dev7-team	dev8-teamr   r   r   task-4.1task-5.1task-6.1task-7.1task-8.1r   r      모든 봇이 작업 중rl  N)r   r%   r   r  r\  rn  rk  rp   rZ   r:   r   r   s        r2   test_all_bots_busy_raises_errorz4TestFindAvailableBot.test_all_bots_busy_raises_error  s    (+==
(3yI(3yI(3yI(3yI(3yI(3yI(3yI(3yI	
 	djj.A]]</IJ 	/,,.	/ 	/ 	/s   /B		Bc                    |dz  dz  }ddddddddddd	i}|j                  t        j                  |      d
       |j                         }d}||k(  }|st	        j
                  d|fd||f      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}}y)u;   completed 상태의 태스크는 봇을 점유하지 않음r   r   rO   r^   r   ro  ry   r}   r   r   r   ri  r`   r   r"  r   r   r   Nrj  rq  s
             r2   test_completed_tasks_dont_blockz4TestFindAvailableBot.test_completed_tasks_dont_block  s    (+==
(3{K(3{K(3{K
 	djj.A113  v    v      v   v          rJ   c                    |dz  dz  }ddddddii}|j                  t        j                  |      d	
       |j                         }d}||k(  }|st	        j
                  d|fd||f      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}}y)uD   마케팅 태스크가 bot-b에서 running이면 bot-b 사용 불가r   r   rO   r   r  r   ri  r   r   botr   r   rp  r`   r   r"  r   r   r   Nrj  rq  s
             r2   $test_marketing_on_bot_b_blocks_bot_bz9TestFindAvailableBot.test_marketing_on_bot_b_blocks_bot_b  s    (+==
yQXY

 	djj.A113  v    v      v   v          rJ   c                    |dz  dz  }|j                         r|j                          |j                         }d}||k(  }|st        j                  d|fd||f      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}}y
)u5   timer 파일이 없으면 모든 봇 가용 → bot-br   r   ri  r`   r   r"  r   r   r   N)r"   unlinkrk  rj   rk   r   r   r   rl   rm   rn   rl  s	            r2    test_no_timer_file_returns_bot_bz5TestFindAvailableBot.test_no_timer_file_returns_bot_b  s    (+==
113  v    v      v   v          rJ   c                    |dz  dz  }|j                  dd       |j                         }d}||k(  }|st        j                  d|fd||f      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}}y)u@   timer 파일이 깨진 JSON이면 모든 봇 가용으로 처리r   r   rk  r   r   ri  r`   r   r"  r   r   r   N)
r   rk  rj   rk   r   r   r   rl   rm   rn   rl  s	            r2   !test_corrupted_json_returns_bot_bz6TestFindAvailableBot.test_corrupted_json_returns_bot_b$  s    (+==
/'B113  v    v      v   v          rJ   N)r   r   r   r   rm  rr  rv  r  r  r  r  r  rQ   rJ   r2   rg  rg    s-    B!!!/&!
!!!rJ   rg  c                   (    e Zd ZdZd Zd Zd Zd Zy)TestPatchTimerMetadatau8   _patch_timer_metadata() 메타데이터 패치 테스트c                    |dz  dz  }dddddii}|j                  t        j                  |      d	       |j                  ddd
       t        j                  |j                               }|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}}y)u3   기존 태스크에 role/bot 메타데이터 추가r   r   rO   r   r  r   ro  r   r   ri  rD  r  rD  r`   rb   rc   rf   rg   Nr  r   r%   r   _patch_timer_metadatar   r   rj   rk   rl   rm   rn   rp   rZ   r:   r   r   r"  rq   rr   rs   rt   ru   s              r2   test_patches_existing_taskz1TestPatchTimerMetadata.test_patches_existing_task4  s$   (+==
*+&STUdjj.A**:KW*UJ0023gz*62AkA2kAAAA2kAAA2AAAkAAAAAAAgz*51<W<1W<<<<1W<<<1<<<W<<<<<<<rJ   c                    |dz  dz  }di i}|j                  t        j                  |      d       |j                  ddd	       t        j                  |j                               }d}|d   }||v}|slt        j                  d
|fd||f      t        j                  |      t        j                  |      dz  }	dd|	iz  }
t        t        j                  |
            dx}x}}y)u9   존재하지 않는 태스크에 패치 시 에러 없음r   r   rO   r   r   r3  r  ri  r  rc  )z%(py1)s not in %(py4)src   rf   rg   Nr  r  s              r2   test_nonexistent_task_no_errorz5TestPatchTimerMetadata.test_nonexistent_task_no_error>  s    (+==
}djj.A**<kw*WJ002326'?2|?2222|?222|222?2222222rJ   c                 |    |dz  dz  }|j                         r|j                          |j                  ddd       y)u'   timer 파일이 없어도 에러 없음r   r   r   r_   ri  r  N)r"   r  r  ro  s       r2   test_no_timer_file_no_errorz2TestPatchTimerMetadata.test_no_timer_file_no_errorG  s?    (+==
**:F*PrJ   c                    |dz  dz  }ddddddii}|j                  t        j                  |      d	
       |j                  ddd       t        j                  |j                               }|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   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)u2   기존 필드는 유지하면서 새 필드 추가r   r   rO   r   r^   r   testr   r   descriptionr   r   r_   ri  r  r   r`   rb   rc   rf   rg   Nr  rD  r  r  s              r2   test_preserves_existing_fieldsz5TestPatchTimerMetadata.test_preserves_existing_fieldsO  s   (+==
*+ci&jkldjj.A**:F*PJ0023gz*95DD5DDDD5DDD5DDDDDDDDDDgz*=9CVC9VCCCC9VCCC9CCCVCCCCCCCgz*62<f<2f<<<<2f<<<2<<<f<<<<<<<rJ   N)r   r   r   r   r  r  r  r  rQ   rJ   r2   r  r  1  s    B=3Q	=rJ   r  c                   6    e Zd ZdZd	dZd Zd Zd Zd Zd Z	y)
TestMarketingConsultingDispatchu*   마케팅/컨설팅 팀 dispatch 테스트Nc                 v    t               }||_        ||nt        j                  ddi      |_        ||_        |S Nr   r  r  r  s        r2   r  z5TestMarketingConsultingDispatch._make_mock_subprocessc  =    k!+'-'9Vtzz8UYJZ?[#rJ   c           
         |dz  dz  }|j                  t        j                  di i      d       | j                  dt        j                  ddi            }t	        j
                  |d	      5 }t	        j
                  |d
ddddd      5  ||j                  _        |j                  dd      }d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}	}y# 1 sw Y   xY w# 1 sw Y   xY w)u   marketing 팀 dispatch 성공r   r   rO   r   r   r   r   r  r  r  r  r  r  r  r  r  u   마케팅 콘텐츠 작성Nr  r`   rb   rc   rf   rg   r/   r   r%   r   r  r   r  r  r  r<   rj   rk   rl   rm   rn   rp   rZ   r:   r   r  r  r"  rq   rr   rs   rt   ru   s               r2   test_marketing_dispatch_successz?TestMarketingConsultingDispatch.test_marketing_dispatch_successj  sx   (+==
djj'27'J00DJJ$?O4PQ LL|4		V8@LL	R		V )4HLL%!**;8TUF		V 		V h/</<////<//////<///////f~,,~,,,,~,,,~,,,,,,,,,,		V 		V 		V 		V$   .G$G /G G
	GGc           
         |dz  dz  }|j                  t        j                  di i      d       | j                  dt        j                  ddi            }t	        j
                  |d	      5 }t	        j
                  |d
ddddd      5  ||j                  _        |j                  dd      }d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}	}y# 1 sw Y   xY w# 1 sw Y   xY w)u   consulting 팀 dispatch 성공r   r   rO   r   r   r   r   r  r  r  r  r  r  r  r  r  u   보험 약관 비교Nr  r`   rb   rc   rf   rg   r/   r  r  s               r2    test_consulting_dispatch_successz@TestMarketingConsultingDispatch.test_consulting_dispatch_success~  sx   (+==
djj'27'J00DJJ$?O4PQ LL|4		Q8@LL	R		Q )4HLL%!**<9OPF		Q 		Q h/</<////<//////<///////f~--~----~---~----------		Q 		Q 		Q 		Qr  c                    |dz  dz  }ddddddddddd	ddd
ddddddddddddi}|j                  t        j                  |      d       t        j                  |d      5 }t        j                  |ddddddddddd	      5  t        ddd       |j                  _        |j                  d!d"      }d#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,   }||v }	|	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# 1 sw Y   xY w)/u<   모든 봇이 사용 중일 때 marketing dispatch → errorr   r   rO   r^   r   ro  ry   r}   rx  ry  rz  r{  r|  r}  r   r   r  r  r  r  r  key4key5key6key7key8r  )	r_   rz   r~   dev4dev5dev6dev7dev8r  r   r  r   r  r  r  Nr   r  r`   rb   rc   rf   rg   r  r  r   r  )r   r%   r   r   r  r   r  r  r<   rj   rk   rl   rm   rn   rp   rZ   r:   r   r   r  r"  rq   rr   rs   rt   ru   s               r2   *test_marketing_all_bots_busy_returns_errorzJTestMarketingConsultingDispatch.test_marketing_all_bots_busy_returns_error  s   (+==
(3yI(3yI(3yI(3yI(3yI(3yI(3yI(3yI	
 	djj.A LL|4	L8@LL""""""""$
	L$ )2QtTV(WHLL%!**;8JKF'	L 	L* h*7*7****7******7*******)>VI->>)->>>>>)->>>>)>>>->>>>>>>>-	L 	L 	L 	Ls$   *"G0G<GG	GG#c           	         |dz  dz  }|j                  t        j                  di i      d       t        j                  |dddd	d
d      5  |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   }||v }|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)uA   선택된 봇의 키가 None이면 error 반환 (sys.exit 아님)r   r   rO   r   r   r  Nr  r  r  r  r  u   봇 키 테스트r   r  r`   rb   rc   rf   rg   r  r  r   r  )r   r%   r   r   r  r<   rj   rk   rl   rm   rn   )
rp   rZ   r:   r   r"  rq   rr   rs   rt   ru   s
             r2   )test_marketing_bot_key_none_returns_errorzITestMarketingConsultingDispatch.test_marketing_bot_key_none_returns_error  s'   (+==
djj'27'J\\66)L
 	M
 "**;8KLF	M h*7*7****7******7*******8MF9<MM8<MMMMM8<MMMM8MMM<MMMMMMMM	M 	Ms   E((E2c           
         |dz  dz  }|j                  t        j                  di i      d       | j                  dt        j                  ddi            }t	        j
                  |d	      5 }t	        j
                  |d
ddddd      5  ||j                  _        |j                  dd      }ddd       ddd       t        j                  |j                               }d   }d|v r|n| d}	|	|d   v r|d   |	   }
|
j                  }d} ||      }d}||k(  }|st        j                  d|fd||f      t        j                  |
      t        j                  |      t        j                  |      t        j                  |      t        j                  |      dz  }dd|iz  }t        t        j                   |            dx}
x}x}x}x}}|d   |	   }
|
j                  }d} ||      }d}||v }|st        j                  d|fd||f      t        j                  |
      t        j                  |      t        j                  |      t        j                  |      t        j                  |      dz  }dd|iz  }t        t        j                   |            dx}
x}x}x}x}}yy# 1 sw Y   xY w# 1 sw Y   	xY w) u6   마케팅 dispatch 시 role/bot 메타데이터 기록r   r   rO   r   r   r   r   r  r  r  r  r  r  r  r  r  u   메타데이터 테스트Nr   r`  z.1rD  r`   rR  rS  assert %(py12)srV  r  )ri  rp  ru  r   )zJ%(py7)s
{%(py7)s = %(py3)s
{%(py3)s = %(py1)s.get
}(%(py5)s)
} in %(py10)s)r   r%   r   r  r   r  r  r  r<   r   r   r!   rj   rk   rl   rm   rn   )rp   rZ   r:   r   r  r  r"  r   r   	timer_keyrq   rs   r   r   r  r   rZ  r[  s                     r2    test_marketing_metadata_recordedz@TestMarketingConsultingDispatch.test_marketing_metadata_recorded  sQ   (+==
djj'27'J00DJJ$?O4PQ LL|4		U8@LL	R		U )4HLL%!**;8STF		U 		U zz*..01#"g~GgYb>	W%=+F+//FF/7F;F7;FFFF7;FFF+FFF/FFFFFF7FFF;FFFFFFFF=+U+//UU/6U:UU6:UUUUU6:UUUU+UUU/UUUUUU6UUU:UUUUUUUUU &!		U 		U 		U 		Us$   .J>$J1/J>1J;	6J>>Kr  )
r   r   r   r   r  r  r  r  r  r  rQ   rJ   r2   r  r  `  s&    4-(.('?RNVrJ   r  c                   *    e Zd ZdZddZd Zd Zd Zy)TestTaskIdReplacementu   task_desc 첫 줄의 플레이스홀더 task-id가 실제 task_id로 교체되어 저장되어야 함.

    현재 dispatch.py는 task_desc를 그대로 저장하므로 이 테스트는 실패(RED).
    Nc                 v    t               }||_        ||nt        j                  ddi      |_        ||_        |S r  r  r  s        r2   r  z+TestTaskIdReplacement._make_mock_subprocess  r  rJ   c                    d}| j                  dt        j                  ddi            }t        j                  |d      5 }t        j                  |dddd	      5  ||j
                  _        |j                  d
|      }d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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}}|j#                  d      }|j%                         d   }d| d}	||	k(  }|st        j                  d|fd||	f      dt        j                         v st        j                   |      rt        j                  |      ndt        j                  |	      dz  }t        j&                  d | d!| d"      d#z   d$|iz  }t        t        j                  |            dx}}	y# 1 sw Y   8xY w# 1 sw Y   =xY w)%u<  task_desc 첫 줄에 '# task-999.1: 제목' 패턴이 있으면
        저장된 파일의 첫 줄이 실제 task_id로 교체되어야 함.

        예: task_desc = "# task-999.1: 테스트 작업

내용"
        생성된 task_id가 task-1.1이면 저장 파일 첫 줄 = "# task-1.1: 테스트 작업"
        uE   # task-999.1: 테스트 작업

상세 내용이 여기 있습니다.r   r   r  r  r  r  r  r  ry   Nr  r`   rb   rc   rf   rg   r   r   rO   r   r   r   r   r   r   # u   : 테스트 작업r   
first_liner   +   첫 줄이 교체되지 않음. 기대: '# u   : 테스트 작업', 실제: ''r=  r   r  r%   r   r   r  r  r  r<   rj   rk   rl   rm   rn   r"   r   r   r   r   
splitlinesr   rp   rZ   r:   r   r  r  r"  rq   rr   rs   rt   ru   actual_task_idr   r   saved_contentr  r   r   s                      r2   *test_task_desc_first_line_task_id_replacedz@TestTaskIdReplacement.test_task_desc_first_line_task_id_replaced  s~    ^	00DJJ$?O4PQ LL|4	C8@LLzF93UV	C )4HLL%!**;	BF	C 	C h/</<////<//////<///////	*x''1~6Fc4JJ	!!!!!!!!!y!!!y!!!!!!!!!!!!!!++W+="--/2
 ~..@A	uAA	uctct	uA	u 	untnt	u 	u\t\t 	u 	uktkt 	u 	uktkt B	u 	uctct88HHfgqfrrst	u 	u 	uatat	u 	u!	C 	C 	C 	Cs$    K$J5?K5J?	:KKc                    d}| j                  dt        j                  ddi            }t        j                  |d      5 }t        j                  |dddd	      5  ||j
                  _        |j                  d
|      }d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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}}|j#                  d      }|j%                         d   }d}	||	k(  }|st        j                  d|fd||	f      dt        j                         v st        j                   |      rt        j                  |      ndt        j                  |	      dz  }t        j&                  d| d       d!z   d"|iz  }t        t        j                  |            dx}}	y# 1 sw Y   1xY w# 1 sw Y   6xY w)#uY   task_desc 첫 줄에 task-id 패턴이 없으면 내용이 그대로 저장되어야 함.u/   # 일반 제목

내용이 여기 있습니다.r   r   r  r  r  r  r  r  ry   Nr  r`   rb   rc   rf   rg   r   r   rO   r   r   r   r   r   r   u   # 일반 제목r   r  r   uK   첫 줄이 의도치 않게 변경됨. 기대: '# 일반 제목', 실제: 'r  r=  r   r  r  s                      r2   (test_task_desc_without_task_id_unchangedz>TestTaskIdReplacement.test_task_desc_without_task_id_unchanged  sl   G	00DJJ$?O4PQ LL|4	C8@LLzF93UV	C )4HLL%!**;	BF	C 	C h/</<////<//////<///////	*x''1~6Fc4JJ	!!!!!!!!!y!!!y!!!!!!!!!!!!!!++W+="--/2
 /  	Lz..  	L  	L  	Lz.  	L  	L  	L  	L  	L  	Lz  	L  	L  	Lz  	L  	L  	L.  	L  	L  	L2}  I  ~J  JK  1L  	L  	L  	L  	L  	L  	L!	C 	C 	C 	Cs$    J;$J.?J;.J8	3J;;Kc                 \   d}d}| j                  dt        j                  ddi            }|dz  dz  }d|d	d
dii}|j                  t        j                  |      d       t	        j
                  |d      5 }t	        j
                  |dddd      5  ||j                  _        |j                  d||      }	d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   }
|
|k(  }|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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}}|j%                  d      }|j'                         d   }d&| d'}||k(  }|st        j                  d|fd(||f      d)t        j                         v st        j                   |      rt        j                  |      nd)t        j                  |      d*z  }t        j(                  d+| d,| d-      d.z   d!|iz  }t        t        j                  |            dx}}y# 1 sw Y   xY w# 1 sw Y   xY w)/uC  --task-id로 task_id를 직접 지정한 경우에도
        task_desc 첫 줄의 플레이스홀더가 지정된 task_id로 교체되어야 함.

        예: task_id="task-42.1", task_desc="# task-999.1: 명시 지정 테스트

내용"
        → 저장 파일 첫 줄 = "# task-42.1: 명시 지정 테스트"
        u5   # task-999.1: 명시 지정 테스트

상세 내용.z	task-42.1r   r   r  r   r   rO   r   z2026-01-01T00:00:00)r   reserved_atr   r   r  r  r  r  r  ry   )r   Nr  r`   rb   rc   rf   rg   r   z%(py1)s == %(py3)sexplicit_task_idr	  r   r   r   r   r   r   r  u   : 명시 지정 테스트r   r  r   r  u%   : 명시 지정 테스트', 실제: 'r  r=  )r  r%   r   r   r   r  r  r  r<   rj   rk   rl   rm   rn   r   r   r   r"   r   r  r   )rp   rZ   r:   r   r  r  r   
timer_datar  r"  rq   rr   rs   rt   ru   r   r   r   r   r  r  s                        r2   )test_task_desc_with_explicit_task_id_flagz?TestTaskIdReplacement.test_task_desc_with_explicit_task_id_flag,  s,    N	&00DJJ$?O4PQ (+==
 0ZXm2nop
djj4wG LL|4	]8@LLzF93UV	] )4HLL%!**;	K[*\F	] 	] h/</<////<//////<///////i 4 $44444 $4444 444444$4444$44444444x''17G6H4LL	!!!!!!!!!y!!!y!!!!!!!!!!!!!!++W+="--/2
 /00IJ	~JJ	~l}l}	~J	~ 	~w}w}	~ 	~e}e} 	~ 	~t}t} 	~ 	~t}t} K	~ 	~l}l}89I8JJopzo{{|}	~ 	~ 	~j}j}	~ 	~!	] 	] 	] 	]s$   9N!&N:N!N	N!!N+r  )r   r   r   r   r  r  r  r  rQ   rJ   r2   r  r    s    
u<L."~rJ   r  c                   *    e Zd ZdZddZd Zd Zd Zy)TestDispatchFailureCleanupup  dispatch() 실패 시 task-timers.json에 잔여 task_id가 없어야 함.

    현재 dispatch.py에서 project_id 디렉토리 없음(line 412)은 _cleanup_task를 호출하지 않고
    바로 return하며, dev팀 봇 키 None(line 475)은 sys.exit(1)을 호출하므로
    두 경우 모두 task-timers.json에 orphan 항목이 남는 버그가 있음(RED).
    Nc                 r    |dz  dz  }d|xs i i}|j                  t        j                  |      d       |S )u+   테스트용 빈 task-timers.json 초기화r   r   rO   r   r   r   r%   r   )rp   r:   extra_tasksr   r   s        r2   _setup_timer_filez,TestDispatchFailureCleanup._setup_timer_file^  sC    (+==
*+djj.ArJ   c                     |dz  dz  }|j                         si S t        j                  |j                  d            j	                  di       S )u4   현재 task-timers.json의 tasks 딕셔너리 반환r   r   r   r   rO   )r"   r%   r   r   r!   )rp   r:   r   s      r2   _get_timer_tasksz+TestDispatchFailureCleanup._get_timer_taskse  sM    (+==
  "Izz*...@AEEgrRRrJ   c                 R   | j                  |       t               }d|_        d|_        d|_        t        j                  |d      5 }t        j                  |dddd      5  ||j                  _        |j                  dd	d
      }d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   }||v }|slt        j                  d|fd||f      t        j                  |      t        j                  |      dz  }	dd|	iz  }
t        t        j                  |
            dx}x}}| j                  |      }|j!                         D cg c]  \  }}|j#                  d      dv s| }}}t%        |      }d}||k(  }|st        j                  d|fd||f      dt'        j(                         v st        j*                  t$              rt        j                  t$              nddt'        j(                         v st        j*                  |      rt        j                  |      ndt        j                  |      t        j                  |      dz  }
t        j,                  d|       dz   d|
iz  }t        t        j                  |            dx}x}}y# 1 sw Y   xY w# 1 sw Y   xY wc c}}w ) u	  project_id를 지정했는데 해당 디렉토리가 없으면
        error를 반환하고 task-timers.json에 해당 task_id가 남아있지 않아야 함.

        현재 dispatch.py line 412에서 _cleanup_task 없이 return → orphan 발생(버그).
        r   r   r  r  r  r  r  r^   u   프로젝트 없는 작업znonexistent-project-xyzr   Nr   r  r`   rb   rc   rf   rg   r  r  r   r  r   r   r  r   
orphan_idsr   >   실패 후 task-timers.json에 orphan 항목이 남아있음: r   r   )r  r   r  r  r  r   r  r  r  r<   rj   rk   rl   rm   rn   r  rF  r!   r   r   r   r   r   )rp   rZ   r:   r  r  r"  rq   rr   rs   rt   ru   rO   r1   entryr  r   r   r   s                     r2   &test_cleanup_on_project_dir_not_existszATestDispatchFailureCleanup.test_cleanup_on_project_dir_not_existsl  s@    	x(  k!" LL|4		8@LLzF93UV		 )4HLL%!**,4 + F		 		 h*7*7****7******7*******!6VI%66!%66666!%6666!666%66666666 %%h/,1KKMljc5UYYx=PTk=kcl
l:r!r!#rrr!rrrrrrsrrrsrrrrrr:rrr:rrrrrr!rrr'efpeq%rrrrrrrr'		 		 		 		$ ms0   L"&L	L;L#L#	L	LL c                    | j                  |       t               }d|_        d|_        d|_        t        j                  |d      5 }t        j                  |dddd      5  ||j                  _        |j                  dd	      }ddd       ddd       t        t              }|s!t        j                  d
      dz   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dt        j                         v st        j                   t              rt        j"                  t              ndt        j"                  |      dz  }t%        t        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}	}| j+                  |      }|j-                         D cg c]  \  }}|j/                  d      dv s| }}}t1        |      }	d}|	|k(  }|st        j(                  d|fd|	|f      dt        j                         v st        j                   t0              rt        j"                  t0              nddt        j                         v st        j                   |      rt        j"                  |      ndt        j"                  |	      t        j"                  |      dz  }
t        j                  d|       dz   d|
iz  }t%        t        j&                  |            dx}	x}}y# 1 sw Y   :xY w# 1 sw Y   ?xY wc c}}w )uB  dev팀 봇 키가 None일 때 error를 반환하고
        task-timers.json에 해당 task_id가 남아있지 않아야 함.

        현재 dispatch.py line 475에서 sys.exit(1) 호출 → _cleanup_task 미호출(버그).
        이 테스트는 sys.exit 대신 error dict를 반환하는 동작을 기대함.
        r   r   r  r  Nr  r  ry   u   봇 키 없는 작업u?   봇 키 None 시 sys.exit 대신 error dict를 반환해야 함z7
>assert %(py4)s
{%(py4)s = %(py0)s(%(py1)s, %(py2)s)
}r!  r"  r=  r#  r   r  r`   rb   rc   rf   rg   r  r  r   r  r   r  r   r   )r  r   r  r  r  r   r  r  r  r<   r!  r=  rj   r   r   r   r   rl   rm   rn   rk   r  rF  r!   r   )rp   rZ   r:   r  r  r"  rr   rt   rq   rs   ru   rO   r1   r  r  r   r   r   s                     r2   test_cleanup_on_bot_key_missingz:TestDispatchFailureCleanup.test_cleanup_on_bot_key_missing  s|    	x(k!" LL|4	Q8@LLzD3ST	Q )4HLL% "**;8OPF	Q 	Q &$'j'jj)jjjjjjjzjjjzjjjjjj&jjj&jjjjjj$jjj$jjj'jjjjjjh*7*7****7******7******* %%h/,1KKMljc5UYYx=PTk=kcl
l:r!r!#rrr!rrrrrrsrrrsrrrrrr:rrr:rrrrrr!rrr'efpeq%rrrrrrrr!	Q 	Q 	Q 	Q ms0   O"$N>O0OO>O	OOr  )r   r   r   r   r  r  r  r  rQ   rJ   r2   r  r  V  s    S!sFsrJ   r  c                   @    e Zd ZdZddedefdZd Zd Zd Z	d Z
d	 Zy
)TestDispatchPhasedChaininguU   dispatch() + phases 파라미터로 chain_manager.py create를 호출하는 테스트r  r  c                 D    t               }||_        ||_        d|_        |S )u+   subprocess.run mock 결과를 생성한다.r   )r   r  r  r  )rp   r  r  mocks       r2   _make_dispatch_mockz.TestDispatchPhasedChaining._make_dispatch_mock  s#    {$rJ   c                 B   ddl }g fd}t        j                  |d      5 }t        j                  |dddd      5  ||j                  _        |j
                  |_        |j                  d	d
dd      }d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 cg c]  }t        |d          }}D cg c]&  }dt        |d         v sdt        |d         v s%|( }}t        |      }	d}|	|k\  }|st        j                  d|fd|	|f      dt        j                         v st        j                   t              rt        j                  t              nddt        j                         v st        j                   |      rt        j                  |      ndt        j                  |	      t        j                  |      dz  }t        j"                  d|       dz   d |iz  }t        t        j                  |            dx}	x}}y# 1 sw Y   	xY w# 1 sw Y   xY wc c}w c c}w )!uQ   --phases 5로 dispatch 시 chain_manager.py create가 subprocess로 호출된다.r   Nc                      j                  | r| d   ng |d       t               }d|_        t        j                  ddi      |_        d|_        |S rI  rJ  rK  s      r2   mock_runzTTestDispatchPhasedChaining.test_dispatch_with_phases_creates_chain.<locals>.mock_run  rN  rJ   r  r  r  r  r  r^   !   # task-566.1: 체이닝 테스트
task-566.1r   r   phasesr   r  r`   rb   rc   rf   rg   r|  chain_manager.pycreater6   ra  rc  r   chain_create_callsr   u9   chain_manager.py create 호출이 없음. 실제 호출: r   r   )r  r   r  r  r  r  r<   rj   rk   rl   rm   rn   r=   r   r   r   r   r   )rp   rZ   r:   r  r  r  r"  rq   rr   rs   rt   ru   rW  all_cmdsr  r   r   r   rL  s                     @r2   'test_dispatch_with_phases_creates_chainzBTestDispatchPhasedChaining.test_dispatch_with_phases_creates_chain  s   ,		 LL|4	8@LLzF93UV	 (0HLL$&5&D&DH#!**3$	 + F	 	 h/</<////<//////</////// -66qC&	N66 
$6#ai.$HXY\]^_e]fYgMgA
 
 %&s!s&!+sss&!sssssssssssssssss%sss%sss&sss!sss/hiqhr-ssssssss+	 	 	 	" 7
s:   J
8I=6J

J%J;JJ=J	J

Jc                 :   ddl }g fd}t        j                  |d      5 }t        j                  |dddd      5  ||j                  _        |j
                  |_        |j                  d	d
dd       ddd       ddd       t              }d}||k\  }|st        j                  d|fd||f      dt        j                         v st        j                  t              rt        j                  t              nddt        j                         v st        j                        rt        j                        ndt        j                  |      t        j                  |      dz  }	t        j                  d      dz   d|	iz  }
t        t        j                   |
            dx}x}}d   }|j#                  d      dz   }||   }t%        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   }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!}||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#}||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}||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"}||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}||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# 1 sw Y   xY w)%uW   phases=3이면 task-566.1, task-566.2, task-566.3 형식의 tasks JSON이 생성된다.r   Nc                      | r| d   ng }dt        |      v rdt        |      v rj                  |       t               }d|_        t	        j
                  ddi      |_        d|_        |S Nr   r  r  r   r  r   r=   r  r   r  r%   r   r  r  r|  r}  r  r  captured_chain_argss       r2   r  z_TestDispatchPhasedChaining.test_dispatch_phases_generates_correct_chain_tasks.<locals>.mock_run  h    !$q'rC!SX-(c#h2F#**3/#+K%&K"!%Xt,<!=K!#KrJ   r  r  r  r  r  r^   r  r  r_  r  r6   ra  rc  r   r  r   (   chain_manager.py create 호출이 없음r   r   z--tasksr`   r  rO   r  r   rb   rc   rf   rg   z
task-566.2ry  z
task-566.3order)r  r   r  r  r  r  r<   r   rj   rk   r   r   r   rl   r   rm   rn   indexr%   r   )rp   rZ   r:   r  r  r  rs   r   r   ru   r   r  	tasks_idx
tasks_jsonrO   rq   rr   rt   r  s                     @r2   2test_dispatch_phases_generates_correct_chain_taskszMTestDispatchPhasedChaining.test_dispatch_phases_generates_correct_chain_tasks  s   ,$&	 LL|4	8@LLzF93UV	 (0HLL$&5&D&DH#!!3$	 " 	 	 &'X1X'1,XXX'1XXXXXXsXXXsXXXXXX&XXX&XXX'XXX1XXX.XXXXXXXX!!$IIi(1,	^


:&5zQzQzQss55zQQx	"2l2"l2222"l222"222l2222222Qx	"2l2"l2222"l222"222l2222222Qx	"2l2"l2222"l222"222l2222222Qx %A% A%%%% A%%% %%%A%%%%%%%Qx %A% A%%%% A%%% %%%A%%%%%%%Qx %A% A%%%% A%%% %%%A%%%%%%%5	 	 	 	s"   X8X6XX	XXc                 J   ddl }g fd}t        j                  |d      5 }t        j                  |dddd      5  ||j                  _        |j
                  |_        |j                  d	d
dd       ddd       ddd       t              }d}||k\  }|st        j                  d|fd||f      dt        j                         v st        j                  t              rt        j                  t              nddt        j                         v st        j                        rt        j                        ndt        j                  |      t        j                  |      dz  }	t        j                  d      dz   d|	iz  }
t        t        j                   |
            dx}x}}d   }|j#                  d      dz   }||   }d}||k(  }|st        j                  d|fd||f      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      dz  }t        j                  d|       dz   d|iz  }t        t        j                   |            dx}}y# 1 sw Y   xY w# 1 sw Y   xY w) uP   task_id에서 base 번호를 올바르게 추출하여 chain_id를 생성한다.r   Nc                      | r| d   ng }dt        |      v rdt        |      v rj                  |       t               }d|_        t	        j
                  ddi      |_        d|_        |S r  r  r  s       r2   r  z\TestDispatchPhasedChaining.test_dispatch_phases_extracts_base_from_task_id.<locals>.mock_run  r  rJ   r  r  r  r  r  r^   r  r  ry  r  r6   ra  rc  r   r  r   r  r   r   z
--chain-id
scoped-566r`   r   r   r   u!   chain_id가 예상값과 다름: r=  r   )r  r   r  r  r  r  r<   r   rj   rk   r   r   r   rl   r   rm   rn   r  )rp   rZ   r:   r  r  r  rs   r   r   ru   r   r  chain_id_idxr   r   r   r   r  s                    @r2   /test_dispatch_phases_extracts_base_from_task_idzJTestDispatchPhasedChaining.test_dispatch_phases_extracts_base_from_task_id  s   ,$&	 LL|4	8@LLzF93UV	 (0HLL$&5&D&DH#!!3$	 " 	 	 &'X1X'1,XXX'1XXXXXXsXXXsXXXXXX&XXX&XXX'XXX1XXX.XXXXXXXX!!$yy.2|$'Wx<'WWWx<WWWWWWxWWWxWWW<WWW+LXJ)WWWWWWW'	 	 	 	s"   J8J6JJ	JJ"c                 @   ddl }dgfd}t        j                  |d      5 }t        j                  |dddd      5  ||j                  _        |j
                  |_        |j                  d	d
dd      }d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# 1 sw Y   xY w# 1 sw Y   xY w)uP   chain_manager.py create 실패 시에도 dispatch 자체는 성공해야 한다.r   Nc                      | r| d   ng }dxx   dz  cc<   t               }dt        |      v r#dt        |      v rd|_        d|_        d|_        n#d|_        t        j                  ddi      |_        d|_        |S )	Nr   r6   r  r  r   zchain create errorr   r  )r   r=   r  r  r  r%   r   )r|  r}  r  r  r  s       r2   r  z`TestDispatchPhasedChaining.test_dispatch_chain_failure_does_not_abort_dispatch.<locals>.mock_run=  s    !$q'rCqMQM#+K!SX-(c#h2F)*&%'"%9")*&%)ZZ40@%A"!#KrJ   r  r  r  r  r  r^   r  r  r   r  r   r  r`   rb   rc   rf   rg   r  )rp   rZ   r:   r  r  r  r"  rq   rr   rs   rt   ru   r  s               @r2   3test_dispatch_chain_failure_does_not_abort_dispatchzNTestDispatchPhasedChaining.test_dispatch_chain_failure_does_not_abort_dispatch7  s    ,S
	  LL|4	8@LLzF93UV	 (0HLL$&5&D&DH#!**3$	 + F	 	 h/</<////<//////<///////	 	 	 	s"   D8D7DD	DDc                    ddl }d }t        j                  |d      5 }t        j                  |dddd      5  ||j                  _        |j
                  |_        |j                  d	d
dd      }d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}||v }	|	st        j                  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        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}	}y# 1 sw Y   xY w# 1 sw Y   xY w)u>   phases 지정 시 dispatch 결과에 chain_id가 포함된다.r   Nc                  n    t               }d|_        t        j                  ddi      |_        d|_        |S )Nr   r   r  r   r  )r|  r}  r  s      r2   r  zcTestDispatchPhasedChaining.test_dispatch_result_includes_chain_id_when_phases_set.<locals>.mock_run`  s5    #+K%&K"!%Xt,<!=K!#KrJ   r  r  r  r  r  r^   r  r  r_  r  r   r  r`   rb   rc   rf   rg   r   r   r  r"  r	  u%   dispatch 결과에 chain_id가 없음r=  r   r  )r  r   r  r  r  r  r<   rj   rk   rl   rm   rn   r   r   r   r   )rp   rZ   r:   r  r  r  r"  rq   rr   rs   rt   ru   r   r   s                 r2   6test_dispatch_result_includes_chain_id_when_phases_setzQTestDispatchPhasedChaining.test_dispatch_result_includes_chain_id_when_phases_set\  s   ,	 LL|4	8@LLzF93UV	 (0HLL$&5&D&DH#!**3$	 + F	 	 h/</<////<//////<///////LzV#LLLzVLLLzLLLLLLVLLLVLLLL%LLLLLLLj!1\1!\1111!\111!111\1111111	 	 	 	s"   I
8H=1I
=I	I

IN)r   r   )r   r   r   r   intr=   r  r  r  r	  r  r  rQ   rJ   r2   r  r    s7    _c s #tJ*&X#XJ#0J2rJ   r  c                   ^    e Zd ZdZddZddededefdZd Zd	 Z	d
 Z
d Zd Zd Zd Zd Zy)TestDispatchParallelBlocku  동일 팀에 running 태스크가 있을 때 dispatch 거부(force=False) / 허용(force=True) 테스트.

    P1 요구사항 (task-792.1):
    - force=False(기본값): 동일 팀 running 태스크가 있으면 status="error" 반환 + task_id 포함
    - force=True: 기존처럼 경고만 로깅하고 dispatch 진행
    - running 태스크 없으면: force 무관하게 정상 통과
    - cleanup 보장: 거부 시에도 _cleanup_task() 호출 필수
    Nc                 v    t               }||_        ||nt        j                  ddi      |_        ||_        |S r  r  r  s        r2   r  z/TestDispatchParallelBlock._make_mock_subprocess  r  rJ   r:   r   running_task_idc                 v    |dz  dz  }d||dddii}|j                  t        j                  |      d       |S )	uD   task-timers.json에 지정 팀의 running 태스크를 등록한다.r   r   rO   r   u   기존 진행 중 작업r  r   r   r  )rp   r:   r   r  r   r   s         r2   _setup_running_taskz-TestDispatchParallelBlock._setup_running_task  sV    (+==
&'#="
 	djj.ArJ   c                 x   | j                  |d      }t        j                  |d      5 }t        j                  |dddd      5  | j                  d      |j                  _        |j                  dd	d
      }ddd       ddd       d   }d}||k(  }|st        j                  d|fd||f      t        j                  |      t        j                  |      dz  }	t        j                  d|       dz   d|	iz  }
t        t        j                  |
            dx}x}}y# 1 sw Y   xY w# 1 sw Y   xY w)u\   동일 팀에 running 태스크가 있고 force=False이면 status='error' 반환해야 함.r^   r   r  r  r  r  r  r   
   새 작업FforceNr   r  r`   rb   rc   u-   force=False 시 error 반환 기대, 실제: 
>assert %(py6)srg   r  r   r  r  r  r  r<   rj   rk   rl   r   rm   rn   rp   rZ   r:   
running_idr  r"  rq   rr   rs   rt   ru   s              r2   5test_running_task_same_team_force_false_returns_errorzOTestDispatchParallelBlock.test_running_task_same_team_force_false_returns_error  s
   --h-L
 LL|4	S8@LLzF93UV	S )-(B(B1(EHLL%!**;E*RF	S 	S hd7d7*ddd7dddddd7ddd.[\b[c,dddddddd	S 	S 	S 	Ss#   D05D$:D0$D-	)D00D9c                    | j                  |dd      }t        j                  |d      5 }t        j                  |dddd      5  | j                  d	      |j                  _        |j                  dd
d      }d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   }||v }|st        j                  d|fd||f      t        j                  |      t        j                  |      dz  }	t        j                  d|d          dz   d|	iz  }
t        t        j                  |
            dx}x}}y# 1 sw Y   .xY w# 1 sw Y   3xY w)uG   에러 메시지에 현재 running 태스크 ID가 포함되어야 함.r^   
task-100.1r   r  r  r  r  r  r  r   r  Fr  Nr   r  r`   rb   rc   rf   rg   r  r   r  uN   에러 메시지에 running task ID 'task-100.1' 미포함. 실제 메시지: r  )r  r   r  r  r  r  r<   rj   rk   rl   rm   rn   r   r  s              r2   Btest_running_task_same_team_error_message_contains_running_task_idz\TestDispatchParallelBlock.test_running_task_same_team_error_message_contains_running_task_id  s   --h]i-j
 LL|4	S8@LLzF93UV	S )-(B(B1(EHLL%!**;E*RF	S 	S h*7*7****7******7*******	p"9-	p--	p^o^o	p-	p 	pfofo 	p 	pfofo .	p 	p^o^o[\bcl\m[no	p 	p 	p\o\o	p 	p 	p	S 	S 	S 	Ss#   F45F';F4'F1	,F44F>c           	         | j                  |d       t        j                  |d      5 }t        j                  |dddd      5  | j                  dt	        j
                  d	d
i            |j                  _        |j                  ddd      }ddd       ddd       d	   }d}||k(  }|st        j                  d|fd||f      t        j                  |      t        j                  |      dz  }t        j                  d|       dz   d|iz  }	t        t        j                  |	            dx}x}}y# 1 sw Y   xY w# 1 sw Y   xY w)u[   동일 팀 running 태스크가 있어도 force=True이면 dispatch가 허용되어야 함.r^   r  r  r  r  r  r  r   r   r     강제 작업Tr  Nr  r`   rb   rc   u*   force=True 시 dispatched 기대, 실제: r  rg   r  r   r  r  r%   r   r  r  r<   rj   rk   rl   r   rm   rn   r  s
             r2   6test_running_task_same_team_force_true_allows_dispatchzPTestDispatchParallelBlock.test_running_task_same_team_force_true_allows_dispatch  s     ; ? LL|4	U8@LLzF93UV	U )-(B(B1djjRZ\`QaFb(cHLL%!**;t*TF	U 	U hf<f</fff<ffffff<fff3]^d]e1ffffffff	U 	U 	U 	Us$   EAD:E:E	?EEc           	         |dz  dz  }|j                  t        j                  di i      d       t        j                  |d      5 }t        j                  |ddd	d
      5  | j                  dt        j                  ddi            |j                  _        |j                  ddd      }d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# 1 sw Y   xY w# 1 sw Y   xY w)uO   running 태스크가 없으면 force=False라도 정상 dispatch 되어야 함.r   r   rO   r   r   r  r  r  r  r  r   r   r  r^   u   일반 작업Fr  Nr  r`   rb   rc   rf   rg   r   r%   r   r   r  r  r  r  r<   rj   rk   rl   rm   rn   rp   rZ   r:   r   r  r"  rq   rr   rs   rt   ru   s              r2   (test_no_running_task_dispatches_normallyzBTestDispatchParallelBlock.test_no_running_task_dispatches_normally  s$    (+==
djj'27'J LL|4	V8@LLzF93UV	V )-(B(B1djjRZ\`QaFb(cHLL%!**;u*UF	V 	V h/</<////<//////<///////	V 	V 	V 	Vs%   E
"AD>-E
>E	E

Ec           	         | j                  |dd       t        j                  |d      5 }t        j                  |dddd      5  | j                  d	t	        j
                  d
di            |j                  _        |j                  ddd      }ddd       ddd       d
   }d}||k(  }|st        j                  d|fd||f      t        j                  |      t        j                  |      dz  }t        j                  d|       dz   d|iz  }	t        t        j                  |	            dx}x}}y# 1 sw Y   xY w# 1 sw Y   xY w)uV   다른 팀에 running 태스크가 있어도 현재 팀은 차단하지 않아야 함.ry   
task-200.1r#  r  r  r  r  r  r   r   r  r^   u   다른 팀 있을 때 작업Fr  Nr  r`   rb   rc   u>   다른 팀 running 태스크가 차단하면 안 됨. 실제: r  rg   r'  r  s
             r2   /test_running_task_different_team_does_not_blockzITestDispatchParallelBlock.test_running_task_different_team_does_not_block  s!    	  ;P\ ] LL|4	e8@LLzF93UV	e )-(B(B1djjRZ\`QaFb(cHLL%!**;8V^c*dF	e 	e hz<z</zzz<zzzzzz<zzz3qrxqy1zzzzzzzz	e 	e 	e 	es$   EAD;E;E	 EEc                 
   | j                  |d       g fd}t        j                  |d      5 }t        j                  |dddd      5  t        j                  |d	|
      5  | j                  d      |j                  _        |j                  ddd      }ddd       d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}}t              }d}||k(  }|st        j                  d|fd||f      dt        j                         v st        j                  t              rt        j                  t              nddt        j                         v st        j                        rt        j                        ndt        j                  |      t        j                  |      dz  }
t        j                   d       dz   d|
iz  }t        t        j                  |            dx}x}}y# 1 sw Y   xY w# 1 sw Y   xY w# 1 sw Y   xY w)uK   force=False로 거부 시 _cleanup_task()가 반드시 호출되어야 함.r^   r  c                 (    j                  |        y r  r  r  s    r2   r  zMTestDispatchParallelBlock.test_block_calls_cleanup_task.<locals>.mock_cleanup  r  rJ   r  r  r  r  r  r  r  r   r  Fr  Nr   r  r`   rb   rc   rf   rg   r6   r  r   r  r   u=   _cleanup_task가 정확히 1회 호출되어야 함, 실제: r   r   )r  r   r  r  r  r  r<   rj   rk   rl   rm   rn   r   r   r   r   r   )rp   rZ   r:   r  r  r"  rq   rr   rs   rt   ru   r   r   r   r  s                 @r2   test_block_calls_cleanup_taskz7TestDispatchParallelBlock.test_block_calls_cleanup_task  s     ; ?	+ LL|4	S8@LLzF93UV	S LLLQ	S
 )-(B(B1(EHLL%!**;E*RF	S 	S 	S h*7*7****7******7*******>"yay"a'yyy"ayyyyyysyyysyyyyyy>yyy>yyy"yyyayyy+hiwhx)yyyyyyyy	S 	S 	S 	S 	S 	Ss;   I8I+&5II+#I8I(#I++I5	0I88Jc           	         | j                  |d       t        j                  |d      5 }t        j                  |dddd      5  | j                  dt	        j
                  d	d
i            |j                  _        |j                  ddd      }ddd       ddd       d	   }d}||k7  }|st        j                  d|fd||f      t        j                  |      t        j                  |      dz  }t        j                  d|       dz   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# 1 sw Y   1xY w)uM   force=True일 때 경고만 로깅하고 error를 반환하지 않아야 함.ry   r  r  r  r  r  r  r   r   r  u   force 허용 작업Tr  Nr  )!=)z%(py1)s != %(py4)src   u4   force=True 시 에러 반환하면 안 됨. 실제: r  rg   r  r`   rb   rf   r'  r  s
             r2   +test_force_true_only_logs_warning_not_errorzETestDispatchParallelBlock.test_force_true_only_logs_warning_not_error  sn     ; ? LL|4	[8@LLzF93UV	[ )-(B(B1djjRZ\`QaFb(cHLL%!**;8MUY*ZF	[ 	[ hk7k7*kkk7kkkkkk7kkk.bcibj,kkkkkkkkh/</<////<//////<///////	[ 	[ 	[ 	[s$   GAF:G:G	?GGc                 n   | j                  |d       t        j                  |d      5 }t        j                  |dddd      5  | j                  d      |j                  _        |j                  dd	      }d
d
d
       d
d
d
       d   }d}||k(  }|st        j                  d|fd||f      t        j                  |      t        j                  |      dz  }t        j                  d      dz   d|iz  }	t        t        j                  |	            d
x}x}}y
# 1 sw Y   xY w# 1 sw Y   xY w)uZ   force 파라미터 기본값은 False이어야 함 (명시하지 않으면 거부 동작).r^   r  r  r  r  r  r  r   u   기본값 테스트Nr   r  r`   rb   rc   uS   force 기본값이 False여야 하며, running 태스크 있으면 거부해야 함r  rg   r  r  s
             r2   test_force_default_is_falsez5TestDispatchParallelBlock.test_force_default_is_false	  s=     ; ? LL|4	O8@LLzF93UV	O )-(B(B1(EHLL%!**;8MNF	O 	O h  	B7  	B7*  	B  	B  	B7  	B  	B  	B  	B  	B  	B7  	B  	B  	B  -B  	B  	B  	B  	B  	B  	B  	B	O 	O 	O 	Os#   D+3D8D+D(	$D++D4r  )r^   r"  )r   r   r   r   r  r   r=   r  r   r$  r(  r,  r/  r2  r5  r7  rQ   rJ   r2   r  r  ~  sU    D 3 _b ep g0{z(0BrJ   r  c                       e Zd ZdZd Zd Zy)TestDispatchForceCLIu    --force CLI 플래그 테스트.c           
         |dz  dz  }|j                  t        j                  ddddddii      d	
       |j                  t        dg d       g |j
                  }fd}|j                  |d|       ddl}ddlm} |j                         }	 ||	      5  |j                          ddd       t              }
d}|
|k(  }|st        j                  d|fd|
|f      dt        j                         v st        j                   t              rt        j"                  t              nddt        j                         v st        j                         rt        j"                        ndt        j"                  |
      t        j"                  |      dz  }t        j$                  d      dz   d|iz  }t'        t        j(                  |            dx}
x}}d   }d}||u }
|
st        j                  d|
fd||f      t        j"                  |      t        j"                  |      dz  }t        j$                  dd          d z   d!|iz  }t'        t        j(                  |            dx}x}
}y# 1 sw Y   xY w)"uO   --force 플래그가 있으면 dispatch()에 force=True가 전달되어야 함.r   r   rO   r"  r^   r   u   기존 작업r  r   r   r  )r  r  r^   r     force CLI 테스트z--forcec            	      \    j                  |j                  dd             ddddddd	i d
S )Nr  Fr  r   r^   r  r   r;  r  r   r   r/   r  r   r  r  r  r  r!   r|  r}  captured_forces     r2   mock_dispatchzWTestDispatchForceCLI.test_main_with_force_flag_passes_force_true.<locals>.mock_dispatch6  s@    !!&**We"<= '%#&!4!#	 	rJ   r<   r   Nredirect_stdoutr6   r`   r  r   r@  r   u0   dispatch()가 정확히 1번 호출되어야 함r   r   Tisz%(py1)s is %(py4)src   u8   --force 플래그 시 force=True 전달 기대, 실제: r  rg   )r   r%   r   r   r>   r<   io
contextlibrC  StringIOr  r   rj   rk   r   r   r   rl   r   rm   rn   )rp   rZ   r  r:   r   original_dispatchrA  rG  rC  r,   rs   r   r   ru   r   rq   rr   rt   r@  s                     @r2   +test_main_with_force_flag_passes_force_truez@TestDispatchForceCLI.test_main_with_force_flag_passes_force_true  s    (+==
JJ$'2&/+:'
  	 	
 	k	
 (11	 	L*mD.KKMQ 	 	  >"[a["a'[[["a[[[[[[s[[[s[[[[[[>[[[>[[["[[[a[[[)[[[[[[[[a xDx D(xxx Dxxx xxxDxxx,destuevdw*xxxxxxxx		  	 s   I55I?c                    |dz  dz  }|j                  t        j                  di i      d       |j                  t        dg d       g fd}|j                  |d	|       d
dl}d
dlm} |j                         } ||      5  |j                          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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}||u }	|	st        j                  d|	fd||f      t        j                   |      t        j                   |      dz  }t        j&                  dd
          dz   d|iz  }t#        t        j$                  |            dx}x}	}y# 1 sw Y   xY w)uP   --force 플래그가 없으면 dispatch()에 force=False가 전달되어야 함.r   r   rO   r   r   r  )r  r  r^   r     일반 CLI 테스트c            	      \    j                  |j                  dd             ddddddd	i d
S )Nr  Fr  r   r^   r  r   rM  r  r=  r>  r?  s     r2   rA  z[TestDispatchForceCLI.test_main_without_force_flag_passes_force_false.<locals>.mock_dispatchY  s>    !!&**We"<=&%#&!5!#	 	rJ   r<   r   NrB  r6   r`   r  r   r@  r   r  r   FrD  rF  rc   u6   --force 없을 때 force=False 전달 기대, 실제: r  rg   )r   r%   r   r   r>   rG  rH  rC  rI  r  r   rj   rk   r   r   r   rl   rm   rn   r   )rp   rZ   r  r:   r   rA  rG  rC  r,   rs   r   r   ru   r   rq   rr   rt   r@  s                    @r2   /test_main_without_force_flag_passes_force_falsezDTestDispatchForceCLI.test_main_without_force_flag_passes_force_falseP  s   (+==
djj'27'JC)qr	 	L*mD.KKMQ 	 	  >"'a'"a''''"a''''''s'''s''''''>'''>'''"'''a'''''''a wEw E)www Ewww wwwEwww-cdrstducv+wwwwwwww		  	 s   IIN)r   r   r   r   rK  rO  rQ   rJ   r2   r9  r9    s    *3yj xrJ   r9  c                   (    e Zd ZdZd Zd Zd Zd Zy)TestWarnResearchImplMixu>   _warn_research_impl_mix 함수의 WARNING 로그 출력 검증c                 $   d}t        j                  |j                  d      5 }|j                  |d       |j	                          |j
                  d   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dd       y# 1 sw Y   yxY w)uD   리서치+구현 키워드가 모두 포함된 경우 WARNING 출력G   API 문서를 조사하고 Publisher 파이프라인을 구현하세요warningr   r   z[research-impl-mix]r   r  call_msgr	  r   r   N)r   r  logger_warn_research_impl_mixassert_called_once	call_argsrj   rk   rl   r   r   r   rm   rn   )	rp   rZ   
mixed_desc	mock_warnrU  rq   rs   r   r   s	            r2   &test_mixed_research_impl_emits_warningz>TestWarnResearchImplMix.test_mixed_research_impl_emits_warning{  s    ^
\\,--y9 	5Y00XF((* **1-a0H(4(H4444(H444(444444H444H4444444		5 	5 	5s   CDDc                     d}t        j                  |j                  d      5 }|j                  |d       |j	                          ddd       y# 1 sw Y   yxY w)u3   구현 키워드만 있을 때는 WARNING 미출력uC   Publisher 파이프라인을 구현하고 테스트 작성하세요rT  r   Nr   r  rV  rW  assert_not_called)rp   rZ   	impl_descr[  s       r2   test_impl_only_no_warningz1TestWarnResearchImplMix.test_impl_only_no_warning  sN    Y	\\,--y9 	*Y00HE'')	* 	* 	*   #AAc                     d}t        j                  |j                  d      5 }|j                  |d       |j	                          ddd       y# 1 sw Y   yxY w)u6   리서치 키워드만 있을 때는 WARNING 미출력u;   API 문서를 조사하고 인증 방식을 파악하세요rT  r   Nr^  )rp   rZ   research_descr[  s       r2   test_research_only_no_warningz5TestWarnResearchImplMix.test_research_only_no_warning  sN    U\\,--y9 	*Y00I'')	* 	* 	*rb  c                     d}t        j                  |j                  d      5 }|j                  |d       |j	                          ddd       y# 1 sw Y   yxY w)uN   리서치+구현 혼합이지만 task_type이 research이면 WARNING 미출력rS  rT  researchNr^  )rp   rZ   rZ  r[  s       r2   'test_mixed_but_research_type_no_warningz?TestWarnResearchImplMix.test_mixed_but_research_type_no_warning  sN    ^
\\,--y9 	*Y00ZH'')	* 	* 	*rb  N)r   r   r   r   r\  ra  re  rh  rQ   rJ   r2   rQ  rQ  x  s    H5***rJ   rQ  c                    ddl }ddl}ddlm}  |t        j
                  j                  dd            }t        |      |j                  vr%|j                  j                  dt        |             ddl
}d}| j                  |j                        5  |j                  |       ddd       | j                  D cg c](  }|j                  |j                  k(  s|j                   * }}d |D        }	t#        |	      }
|
st%        j&                  d|       d	z   d
t)        j*                         v st%        j,                  t"              rt%        j.                  t"              nd
t%        j.                  |	      t%        j.                  |
      dz  }t1        t%        j2                  |            dx}	}
y# 1 sw Y   xY wc c}w )u?   3000자 이상 지시서에 대해 WARNING 로그 발생 확인r   Nr   r   r     AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAc              3   $   K   | ]  }d |v  
 ywz[large-task-desc]NrQ   .0msgs     r2   	<genexpr>z,test_warn_large_task_desc.<locals>.<genexpr>  s      '*s"   uC   WARNING 로그에 '[large-task-desc]' 미포함. 기록된 로그: .
>assert %(py4)s
{%(py4)s = %(py0)s(%(py2)s)
}anyr   loggingr>   pathlibr   r   r    r!   r=   r?   r@   r<   at_levelWARNING_warn_large_task_descrecordslevelnor  rs  rj   r   r   r   r   rl   rm   rn   )caplogru  r>   r   r*   rZ   
large_descrw  warning_messagesr   rr   rt   s               r2   test_warn_large_task_descr    s   RZZ^^$46KLMI
9~SXX%3y>*#J		) 7**:67 ,2>>ZaQYY'//=Y		ZZ.> `3   `  `N_N_	LM]L^_` `Y_Y_` `G_G_  ` `V_V_  ` `V_V_ ` `V_V_ ` ` `L_L_` `	7 7 [s   F2:F?F?2F<c                    ddl }ddl}ddlm}  |t        j
                  j                  dd            }t        |      |j                  vr%|j                  j                  dt        |             ddl
}d}| j                  |j                        5  |j                  |       ddd       | j                  D cg c](  }|j                  |j                  k(  s|j                   * }}d |D        }	t#        |	      }
|
 }|st%        j&                  d|       d	z   d
t)        j*                         v st%        j,                  t"              rt%        j.                  t"              nd
t%        j.                  |	      t%        j.                  |
      dz  }t1        t%        j2                  |            dx}	x}
}y# 1 sw Y   xY wc c}w )u?   3000자 미만 지시서에 대해 WARNING 로그 없음 확인r   Nr   r   r     AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAc              3   $   K   | ]  }d |v  
 ywrl  rQ   rm  s     r2   rp  z7test_warn_large_task_desc_under_3000.<locals>.<genexpr>  s      '*s"rq  u>   3000자 미만인데 WARNING 로그 발생. 기록된 로그: z2
>assert not %(py4)s
{%(py4)s = %(py0)s(%(py2)s)
}rs  r   rt  )r|  ru  r>   r   r*   rZ   
small_descrw  r~  r   rr   r   r   s                r2   $test_warn_large_task_desc_under_3000r    s   RZZ^^$46KLMI
9~SXX%3y>*#J		) 7**:67 ,2>>ZaQYY'//=Y		ZZ.> [s   [   [  [IZIZ	GHXGYZ[ [TZTZ[ [BZBZ  [ [QZQZ  [ [QZQZ [ [QZQZ [ [ [GZGZ[ [ [	7 7 [s   F7:GG7Gc                    ddl }ddl}ddlm}  |t        j
                  j                  dd            }t        |      |j                  vr%|j                  j                  dt        |             ddl
}d}| j                  |j                        5  |j                  |d       ddd       | j                  D cg c](  }|j                  |j                  k(  s|j                   * }}d |D        }	t#        |	      }
|
st%        j&                  d	|       d
z   dt)        j*                         v st%        j,                  t"              rt%        j.                  t"              ndt%        j.                  |	      t%        j.                  |
      dz  }t1        t%        j2                  |            dx}	}
y# 1 sw Y   xY wc c}w )uQ   리서치+구현 혼합 경고에 세션 경량화 참조 메시지 포함 확인r   Nr   r   r   rS  r   c              3   $   K   | ]  }d |v  
 yw)z/compactNrQ   rm  s     r2   rp  z:test_warn_research_impl_mix_session_msg.<locals>.<genexpr>  s      !
crq  u:   WARNING 로그에 '/compact' 미포함. 기록된 로그: rr  rs  r   )ru  r>   rv  r   r   r    r!   r=   r?   r@   r<   rw  rx  rW  rz  r{  r  rs  rj   r   r   r   r   rl   rm   rn   )r|  ru  r>   r   r*   rZ   rZ  rw  r~  r   rr   rt   s               r2   'test_warn_research_impl_mix_session_msgr    s   RZZ^^$46KLMI
9~SXX%3y>*#ZJ		) C,,ZBC ,2>>ZaQYY'//=Y		ZZ%5 W3   W  WEVEV	CDTCUVW WPVPVW W>V>V  W WMVY  W WMVY W WMVY W W WCVCVW W	C C [s   F3;G G 3F=c                   4    e Zd ZdZd Zd Zd Zd Zd Zd Z	y)	TestCounterBasedTaskIduN   카운터 파일 기반 generate_task_id() 테스트 (v2 채번 방어 로직)c                    |dz  dz  }|j                  d       |dz  dz  }|j                  t        j                  di i             |j                         }d}||k(  }|st	        j
                  d|fd||f      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}}y)u5   카운터 파일이 있으면 그 값으로 ID 생성r   .task-counter100r   rO   task-100r`   r   r   r   r   r   Nr   
rp   rZ   r:   counter_filer   r   rs   r   r   r   s
             r2   $test_counter_file_determines_next_idz;TestCounterBasedTaskId.test_counter_file_determines_next_id  s    (*_<&(+==
djj'278//1$$w*$$$$w*$$$$$$w$$$w$$$*$$$$$$$rJ   c           	         |dz  dz  }|j                  d       |dz  dz  }|j                  t        j                  di i             |j                          |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                  |      t        j                  |      t        j                  |	      d
z  }dd|iz  }t        t        j                  |            dx}x}x}x}x}
}	y)u+   ID 생성 후 카운터 파일이 +1 증가r   r  50r   rO   51r`   z{%(py8)s
{%(py8)s = %(py6)s
{%(py6)s = %(py4)s
{%(py4)s = %(py2)s
{%(py2)s = %(py0)s.read_text
}()
}.strip
}()
} == %(py11)sr  r   r   re   rg   r   r   r   r   N)r   r%   r   r   r   striprj   rk   r   r   r   rl   rm   rn   )rp   rZ   r:   r  r   r   rr   r   r   r   r  r   r   s                r2   (test_counter_increments_after_generationz?TestCounterBasedTaskId.test_counter_increments_after_generation  s   (*_<%(+==
djj'278%%'%%7%'7'--7-/747/47777/4777777|777|777%777'777-777/777477777777rJ   c                 8   |dz  dz  }|j                  d       |dz  dz  }|j                  t        j                  di i             t        d      D cg c]  }|j	                          }}g d}||k(  }|st        j                  d|fd	||f      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}}yc c}w )u9   연속 호출 시 카운터 기반으로 연속 ID 생성r   r  10r   rO   r_  )ztask-10ztask-11ztask-12r`   r   r   r   r   r   N)r   r%   r   r   r   rj   rk   r   r   r   rl   rm   rn   )rp   rZ   r:   r  r   r   r   rs   r   r   r   s              r2   !test_consecutive_ids_from_counterz8TestCounterBasedTaskId.test_consecutive_ids_from_counter  s    (*_<%(+==
djj'2788=aA1|,,.AA77s77777s7777777s777s77777777777 Bs   Dc                    |dz  dz  }|j                  d       |dz  dz  }|j                  t        j                  ddddiii             |j                         }d	}||k(  }|st	        j
                  d
|fd||f      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}}y)u>   카운터 파일이 손상되면 task-timers.json에서 복구r   r  not_a_numberr   rO   r  r   r   ztask-6r`   r   r   r   r   r   Nr   r  s
             r2   +test_corrupted_counter_falls_back_to_timerszBTestCounterBasedTaskId.test_corrupted_counter_falls_back_to_timers  s    (*_</(+==
djj'J;@W3X)YZ[//1""w(""""w(""""""w"""w"""("""""""rJ   c                    |dz  dz  }|j                  t        j                  ddddiii             |j                         }d}||k(  }|st	        j
                  d|fd	||f      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}}y)u8   카운터 파일 없으면 task-timers.json에서 계산r   r   rO   r   r   r   r   r`   r   r   r   r   r   Nr   	rp   rZ   r:   r   r   rs   r   r   r   s	            r2   )test_missing_counter_falls_back_to_timersz@TestCounterBasedTaskId.test_missing_counter_falls_back_to_timers  s    (+==
djj'J;@W3X)YZ[//1""w(""""w(""""""w"""w"""("""""""rJ   c                    |dz  dz  }|j                  d       |dz  dz  }|j                  t        j                  ddddiii             |j                         }d	}||k(  }|st	        j
                  d
|fd||f      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}}y)ua   카운터(99999)가 timers 최대(4) 대비 1000 이상 큰 경우 → timers 기준으로 보정r   r  99999r   rO   rr  r   r   r   r`   r   r   r   r   r   Nr   r  s
             r2   /test_counter_outlier_1000_over_timers_correctedzFTestCounterBasedTaskId.test_counter_outlier_1000_over_timers_corrected  s    (*_<((+==
djj'Hx>U3V)WXY//1""w(""""w(""""""w"""w"""("""""""rJ   N)
r   r   r   r   r  r  r  r  r  r  rQ   rJ   r2   r  r    s#    X%88###rJ   r  c                   .    e Zd ZdZd Zd Zd Zd Zd Zy)TestOutlierFilteringuc   이상치 ID 필터링 테스트 — 비정상 큰 ID가 채번을 오염시키지 않는지 검증c                    |dz  dz  }|j                  t        j                  dddiddiddiddiddidi             |j                         }d}||k(  }|st	        j
                  d|fd	||f      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}}y)uQ   정상 ID + 비정상적으로 큰 ID가 있을 때 정상 ID 기반으로 채번r   r   rO   r   r   )r   r   r   ztask-9991.1ztask-9992.1r   r`   r   r   r   r   r   Nr   r  s	            r2   test_large_gap_ids_filteredz0TestOutlierFiltering.test_large_gap_ids_filtered&  s    (+==
JJ%-{$;%-{$;%-{$;(0+'>(0+'>
	
 //1""w(""""w(""""""w"""w"""("""""""rJ   c           	         |dz  dz  }|j                  t        j                  dddiddiddidi             |j                         }d}||k(  }|st	        j
                  d	|fd
||f      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}}y)u&   이상치 없으면 기존처럼 max+1r   r   rO   r   r   r   r   r   r`   r   r   r   r   r   Nr   r  s	            r2    test_no_outliers_normal_behaviorz5TestOutlierFiltering.test_no_outliers_normal_behavior:  s    (+==
JJ%-{$;%-{$;%-y$9
	
 //1""w(""""w(""""""w"""w"""("""""""rJ   c                    |dz  dz  }|j                  d       |dz  dz  }|j                  t        j                  dddiddidi             |j                         }d	}||k(  }|st	        j
                  d
|fd||f      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}}y)uH   카운터 파일이 있으면 이상치와 무관하게 카운터 사용r   r  r  r   rO   r   r   )r   ztask-9999.1r  r`   r   r   r   r   r   Nr   r  s
             r2   +test_counter_takes_precedence_over_outliersz@TestOutlierFiltering.test_counter_takes_precedence_over_outliersK  s    (*_<&(+==
JJ%-{$;(0+'>		
 //1$$w*$$$$w*$$$$$$w$$$w$$$*$$$$$$$rJ   c                    |dz  dz  }|j                  t        j                  dddiddidi             |j                         }d}||k(  }|st	        j
                  d|fd	||f      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}}y)u9   갭 999는 정상, 갭 1000 이상은 이상치로 판정r   r   rO   r   r   )r   ztask-1000.1z	task-1001r`   r   r   r   r   r   Nr   r  s	            r2   test_gap_threshold_is_1000z/TestOutlierFiltering.test_gap_threshold_is_1000]  s    (+==
JJ%-{$;(0+'>		
 //1%%w+%%%%w+%%%%%%w%%%w%%%+%%%%%%%rJ   c                    |dz  dz  }|j                  t        j                  dddiddidi             |j                         }d}||k(  }|st	        j
                  d|fd	||f      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}}y)u%   갭이 정확히 1000이면 이상치r   r   rO   r   r   )r   ztask-1001.1r   r`   r   r   r   r   r   Nr   r  s	            r2    test_gap_exactly_1000_is_outlierz5TestOutlierFiltering.test_gap_exactly_1000_is_outliern  s    (+==
JJ%-{$;(0+'>		
 //1""w(""""w(""""""w"""w"""("""""""rJ   N)	r   r   r   r   r  r  r  r  r  rQ   rJ   r2   r  r  #  s    m#(#"%$&"#rJ   r  c                   "    e Zd ZdZd Zd Zd Zy)TestCounterFileEdgeCasesu+   카운터 파일 엣지 케이스 테스트c                    |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}x}}|j                          |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}}y)u,   첫 실행 시 카운터 파일이 생성됨r   r  zEassert not %(py4)s
{%(py4)s = %(py2)s
{%(py2)s = %(py0)s.exists
}()
}r  r   Nr   )	r"   r   r   rj   r   rl   rm   rn   r   )	rp   rZ   r:   r  r   rr   r   r   rt   s	            r2   &test_counter_file_created_on_first_runz?TestCounterFileEdgeCases.test_counter_file_created_on_first_run  s    (*_<&&(&(((((((((((<(((<(((&((((((((((%%'""$"$$$$$$$$|$$$|$$$"$$$$$$$$$$rJ   c                    |dz  dz  }|j                  d       |dz  dz  }|j                  t        j                  ddddiii             |j                         }d	}||k(  }|st	        j
                  d
|fd||f      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}}y)u    빈 카운터 파일은 fallbackr   r  r   r   rO   r  r   r   ztask-8r`   r   r   r   r   r   Nr   r  s
             r2   "test_empty_counter_file_falls_backz;TestCounterFileEdgeCases.test_empty_counter_file_falls_back  s    (*_<#(+==
djj'J;@W3X)YZ[//1""w(""""w(""""""w"""w"""("""""""rJ   c                     |dz  dz  }|j                  d       |dz  dz  }|j                  t        j                  di i             y)uH   음수 카운터도 그대로 사용 (방어는 하되 동작은 유지)r   r  z-5r   rO   Nr  )rp   rZ   r:   r  r   s        r2    test_negative_counter_falls_backz9TestCounterFileEdgeCases.test_negative_counter_falls_back  sL      (*_<%(+==
djj'278rJ   N)r   r   r   r   r  r  r  rQ   rJ   r2   r  r    s    5%#9rJ   r  c                   "    e Zd ZdZd Zd Zd Zy)TestSyncCounterPhaseAwareuS   _sync_counter_if_needed()가 Phase 접미사를 올바르게 무시하는지 검증c           	         |dz  dz  }|j                  d       |j                  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                  |      t	        j                  |      t	        j                  |      d	z  }
d
d|
iz  }t        t	        j                  |            dx}x}x}x}x}	}y)uC   task-1845_2.2 → 기본 번호 1845만 추출하여 카운터 syncr   r  r  ztask-1845_2.21846r`   r  r  r  r   r   Nr   _sync_counter_if_neededr   r  rj   rk   r   r   r   rl   rm   rn   rp   rZ   r:   r  r   rr   r   r   r   r  r   r   s               r2   test_phase_suffix_ignoredz3TestSyncCounterPhaseAware.test_phase_suffix_ignored  s    (*_<&,,_=%%9%'9'--9-/969/69999/6999999|999|999%999'999-999/999699999999rJ   c           	         |dz  dz  }|j                  d       |j                  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                  |      t	        j                  |      t	        j                  |      d	z  }
d
d|
iz  }t        t	        j                  |            dx}x}x}x}x}	}y)u*   task-500_a → 기본 번호 500만 추출r   r  r  z
task-500_a501r`   r  r  r  r   r   Nr  r  s               r2   test_parallel_suffix_ignoredz6TestSyncCounterPhaseAware.test_parallel_suffix_ignored      (*_<&,,\:%%8%'8'--8-/858/58888/5888888|888|888%888'888-888/888588888888rJ   c           	         |dz  dz  }|j                  d       |j                  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                  |      t	        j                  |      t	        j                  |      d	z  }
d
d|
iz  }t        t	        j                  |            dx}x}x}x}x}	}y)u*   task-300+1 → 기본 번호 300만 추출r   r  r  z
task-300+1301r`   r  r  r  r   r   Nr  r  s               r2   test_retry_suffix_ignoredz3TestSyncCounterPhaseAware.test_retry_suffix_ignored  r  rJ   N)r   r   r   r   r  r  r  rQ   rJ   r2   r  r    s    ]:99rJ   r  c                       e Zd ZdZd Zd Zy)TestTaskIdFormatValidationu$   --task-id 포맷 v2 검증 테스트c                 |   t               }d|_        t        j                  ddi      |_        d|_        |j                  t        dg d       t        j                  |d      5 }t        j                  |dd	d
d      5  t        j                  |j                  d      5 }||j                  _        |j                          ddd       ddd       ddd       j                  D cg c]  }dt        |      v s| }}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#        j0                  |            dx}	x}}
y# 1 sw Y   RxY w# 1 sw Y   WxY w# 1 sw Y   \xY wc c}w )u!   task-100 → 경고 없이 통과r   r   r  r   r  )r  r  r^   r  r  	--task-idr  r  r  r  r  r  rT  Ntask-id-formatr`   r  r   format_warningsr   r  r   r   r  r%   r   r  r  r   r>   r   r  rV  r  r  r  call_args_listr=   r   rj   rk   r   r   r   rl   rm   rn   rp   rZ   r  r  r  r  r[  rW  r  rs   r   r   ru   r   s                 r2   test_valid_simple_formatz3TestTaskIdFormatValidation.test_valid_simple_format  s   k!"!ZZ4(89C)  	ALL|4	 8@LLzF93UV	  LL,,i8	  =F(3HLL%	  	  	  '0&>&>]BRVYZ[V\B\1]]?#(q(#q((((#q((((((s(((s((((((?(((?(((#(((q(((((((	  	  	  	  	  	  ^H   $H,?!H "HH
H,(H9;H9HHH)	$H,,H6c                 |   t               }d|_        t        j                  ddi      |_        d|_        |j                  t        dg d       t        j                  |d      5 }t        j                  |dd	d
d      5  t        j                  |j                  d      5 }||j                  _        |j                          ddd       ddd       ddd       j                  D cg c]  }dt        |      v s| }}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#        j0                  |            dx}	x}}
y# 1 sw Y   RxY w# 1 sw Y   WxY w# 1 sw Y   \xY wc c}w )u0   task-잘못된형식 → 경고 메시지 출력r   r   r  r   r  )r  r  r^   r  r  r  u   task-잘못된형식r  r  r  r  r  rT  Nr  r6   r`   r  r   r  r   r  r   r  r  s                 r2   !test_invalid_format_emits_warningz<TestTaskIdFormatValidation.test_invalid_format_emits_warning  s   k!"!ZZ4(89C  *L  	MLL|4	 8@LLzF93UV	  LL,,i8	  =F(3HLL%	  	  	  '0&>&>]BRVYZ[V\B\1]]?#(q(#q((((#q((((((s(((s((((((?(((?(((#(((q(((((((	  	  	  	  	  	  ^r  N)r   r   r   r   r  r  rQ   rJ   r2   r  r    s    .)&)rJ   r  c                   :    e Zd ZdZd Zd Zd Zd Zd Zd Z	d Z
y	)
TestGetBusyBotsInfou8   _get_busy_bots_info() 봇 점유 정보 반환 테스트c                    |dz  dz  }|j                  t        j                  di i      d       |j                         }i }||k(  }|st	        j
                  d|fd||f      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}}y)u2   빈 task-timers.json이면 빈 딕셔너리 반환r   r   rO   r   r   r`   r   r"  r   r   r   Nr   r%   r   _get_busy_bots_inforj   rk   r   r   r   rl   rm   rn   rl  s	            r2   test_empty_timers_returns_emptyz3TestGetBusyBotsInfo.test_empty_timers_returns_empty  s    (+==
djj'27'J113v|vvvrJ   c                    |dz  dz  }|j                         r|j                          |j                         }i }||k(  }|st        j                  d|fd||f      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}}y	)
u1   timer 파일이 없으면 빈 딕셔너리 반환r   r   r`   r   r"  r   r   r   N)r"   r  r  rj   rk   r   r   r   rl   rm   rn   rl  s	            r2    test_no_timer_file_returns_emptyz4TestGetBusyBotsInfo.test_no_timer_file_returns_empty  s    (+==
113v|vvvrJ   c                    |dz  dz  }dddddii}|j                  t        j                  |      d	       |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   }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}
||
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"   dev1-team running → bot-b 점유r   r   rO   r   r^   r   ro  r   r   ri  r   r  r"  r	  r   r   Nr   r`   rb   rc   rf   rg   r   r   r%   r   r  rj   rk   rl   r   r   r   rm   rn   rp   rZ   r:   r   r   r"  rq   rs   r   r   rr   rt   ru   s                r2   !test_dev_team_running_maps_to_botz5TestGetBusyBotsInfo.test_dev_team_running_maps_to_bot	  sU   (+==
*+&STUdjj.A113 w&    w&   w      &   &       gy)7Z7)Z7777)Z777)777Z7777777gy)8[8)[8888)[888)888[8888888rJ   c                    |dz  dz  }ddddddii}|j                  t        j                  |      d	
       |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   }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}
||
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+   composite 태스크의 bot 필드가 반영r   r   rO   r"  	compositer   bot-gr  r   r   r   r  r"  r	  r   r   Nr   r`   rb   rc   rf   rg   r   r  r  s                r2   test_composite_bot_field_mappedz3TestGetBusyBotsInfo.test_composite_bot_field_mapped	  sX   (+==
,K9]d(efgdjj.A113 w&    w&   w      &   &       gy)9\9)\9999)\999)999\9999999gy)8[8)[8888)[888)888[8888888rJ   c                    |dz  dz  }dddddii}|j                  t        j                  |      d	       |j                         }i }||k(  }|st	        j
                  d
|fd||f      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}}y)u1   completed 상태는 점유로 간주하지 않음r   r   rO   r   r^   r   ro  r   r   r`   r   r"  r   r   r   Nr  rq  s
             r2   test_completed_tasks_excludedz1TestGetBusyBotsInfo.test_completed_tasks_excluded	  s    (+==
*+&UVWdjj.A113v|vvvrJ   c                    |dz  dz  }|j                  dd       |j                         }i }||k(  }|st        j                  d|fd||f      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}}y)u)   깨진 JSON이면 빈 딕셔너리 반환r   r   rk  r   r   r`   r   r"  r   r   r   N)
r   r  rj   rk   r   r   r   rl   rm   rn   rl  s	            r2   !test_corrupted_json_returns_emptyz5TestGetBusyBotsInfo.test_corrupted_json_returns_empty!	  s    (+==
/'B113v|vvvrJ   c                    |dz  dz  }dddddddd	d
dddi}|j                  t        j                  |      d       |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}||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)u4   여러 running 태스크가 있을 때 모두 반영r   r   rO   r^   r   ro  r  r  r  r}   r   r   r   r   ri  r   r  r"  r	  r   r   Nru  rc  z%(py1)s not in %(py3)sr  
rp   rZ   r:   r   r   r"  rq   rs   r   r   s
             r2   test_multiple_running_tasksz/TestGetBusyBotsInfo.test_multiple_running_tasks(	  ss   (+==
(3yI(3yQXY(3{K
 	djj.A113 w&    w&   w      &   &        w&    w&   w      &   &       $wf$$$$wf$$$w$$$$$$f$$$f$$$$$$$rJ   N)r   r   r   r   r  r  r  r  r  r  r  rQ   rJ   r2   r  r    s(    B99%rJ   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)TestDevTeamBotConflictu^   dev팀 dispatch 시 composite/dynamic 작업에 봇이 점유되어 있으면 차단 테스트Nc                 v    t               }||_        ||nt        j                  ddi      |_        ||_        |S r  r  r  s        r2   r  z,TestDevTeamBotConflict._make_mock_subprocessA	  r  rJ   c                    |dz  dz  }ddddddii}|j                  t        j                  |      d	
       t        j                  |d      5 }t        j                  |dddd      5  | j                  d      |j                  _        |j                  ddd      }d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   }||v }	|	slt        j                  d|	fd||f      t        j                  |      t        j                  |      dz  }
dd|
iz  }t        t        j                  |            dx}x}	}d}|d   }||v }	|	slt        j                  d|	fd||f      t        j                  |      t        j                  |      dz  }
dd|
iz  }t        t        j                  |            dx}x}	}d}|d   }||v }	|	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# 1 sw Y   xY w) u>   composite가 bot-g 점유 중 → dev6-team dispatch 시 errorr   r   rO   task-1220.1r  r   r  r  r   r   r  r  r  r  r  r  r   rz  r  Fr  Nr   r  r`   rb   rc   rf   rg   r  r   r  r*  r  s               r2   ,test_composite_on_bot_g_blocks_dev6_dispatchzCTestDevTeamBotConflict.test_composite_on_bot_g_blocks_dev6_dispatchH	  s"   (+==
;)T[\

 	djj.A LL|4	Y8@LLzF93UV	Y )-(B(B1(EHLL%!**;8JRW*XF	Y 	Y h*7*7****7******7*******+&++w+++++w++++w+++++++++++/fY//{/////{////{///////////1y 11} 11111} 1111}111 11111111	Y 	Y 	Y 	Y$   J:*5J-J:-J7	2J::Kc                    |dz  dz  }ddddddii}|j                  t        j                  |      d	
       t        j                  |d      5 }t        j                  |dddd      5  | j                  d      |j                  _        |j                  ddd      }d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   }||v }	|	slt        j                  d|	fd||f      t        j                  |      t        j                  |      dz  }
dd|
iz  }t        t        j                  |            dx}x}	}d}|d   }||v }	|	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# 1 sw Y   xY w) u>   composite가 bot-b 점유 중 → dev1-team dispatch 시 errorr   r   rO   r  r  r   ri  r  r   r   r  r  r  r  r  r   r^   r  Fr  Nr   r  r`   rb   rc   rf   rg   r  r   r  r*  r  s               r2   ,test_composite_on_bot_b_blocks_dev1_dispatchzCTestDevTeamBotConflict.test_composite_on_bot_b_blocks_dev1_dispatch^	  s   (+==
;)T[\

 	djj.A LL|4	Y8@LLzF93UV	Y )-(B(B1(EHLL%!**;8JRW*XF	Y 	Y h*7*7****7******7*******+&++w+++++w++++w+++++++++++1y 11} 11111} 1111}111 11111111	Y 	Y 	Y 	Y$   H;*5H.H;.H8	3H;;Ic           	         |dz  dz  }|j                  t        j                  di i      d       t        j                  |d      5 }t        j                  |ddd	d
      5  | j                  dt        j                  ddi            |j                  _        |j                  dd      }d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# 1 sw Y   xY w# 1 sw Y   xY w)u?   composite 없을 때 dev6-team 정상 위임 (회귀 테스트)r   r   rO   r   r   r  r  r  r  r  r   r   r  rz     정상 작업Nr  r`   rb   rc   rf   rg   r*  r+  s              r2   &test_no_composite_allows_dev6_dispatchz=TestDevTeamBotConflict.test_no_composite_allows_dev6_dispatchs	  s   (+==
djj'27'J LL|4	I8@LLzF93UV	I )-(B(B1djjRZ\`QaFb(cHLL%!**;HF	I 	I h/</<////<//////<///////	I 	I 	I 	Is%   E"A	D<+E<E	EEc           	         |dz  dz  }ddddddii}|j                  t        j                  |      d	
       t        j                  |d      5 }t        j                  |dddd      5  | j                  dt        j                  ddi            |j                  _        |j                  ddd      }d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# 1 sw Y   xY w# 1 sw Y   xY w)uN   composite가 bot-g 점유 중이라도 force=True면 dev6-team dispatch 허용r   r   rO   r  r  r   r  r  r   r   r  r  r  r  r  r   r   r  rz  r&  Tr  Nr  r`   rb   rc   rf   rg   r*  r  s               r2   .test_composite_on_bot_g_force_true_allows_dev6zETestDevTeamBotConflict.test_composite_on_bot_g_force_true_allows_dev6	  s<   (+==
;)T[\

 	djj.A LL|4	U8@LLzF93UV	U )-(B(B1djjRZ\`QaFb(cHLL%!**;t*TF	U 	U h/</<////<//////<///////	U 	U 	U 	Us%   E*AE5EE	EEc           	         |dz  dz  }ddddddii}|j                  t        j                  |      d	
       t        j                  |d      5 }t        j                  |dddd      5  | j                  dt        j                  ddi            |j                  _        |j                  dd      }d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# 1 sw Y   xY w# 1 sw Y   xY w)uI   composite가 completed면 봇 점유 아님 → dev6-team 정상 dispatchr   r   rO   r  r  r   r  r  r   r   r  r  r  r  r  r   r   r  rz  r  Nr  r`   rb   rc   rf   rg   r*  r  s               r2   -test_composite_completed_allows_dev6_dispatchzDTestDevTeamBotConflict.test_composite_completed_allows_dev6_dispatch	  s7   (+==
;+V]^

 	djj.A LL|4	I8@LLzF93UV	I )-(B(B1djjRZ\`QaFb(cHLL%!**;HF	I 	I h/</<////<//////<///////	I 	I 	I 	I%   E*A	E3EE		EEc                    |dz  dz  }ddddddii}|j                  t        j                  |      d	
       t        j                  |d      5 }t        j                  |dddd      5  | j                  d      |j                  _        |j                  ddd      }d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   }||v }	|	slt        j                  d|	fd||f      t        j                  |      t        j                  |      dz  }
dd|
iz  }t        t        j                  |            dx}x}	}d}|d   }||v }	|	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# 1 sw Y   xY w) u>   marketing이 bot-g 점유 중 → dev6-team dispatch 시 errorr   r   rO   
task-500.1r  r   r  r  r   r   r  r  r  r  r  r   rz  r  Fr  Nr   r  r`   rb   rc   rf   rg   r  r   r  r*  r  s               r2   ,test_marketing_on_bot_g_blocks_dev6_dispatchzCTestDevTeamBotConflict.test_marketing_on_bot_g_blocks_dev6_dispatch	  s   (+==
+SZ[

 	djj.A LL|4	Y8@LLzF93UV	Y )-(B(B1(EHLL%!**;8JRW*XF	Y 	Y h*7*7****7******7*******+&++w+++++w++++w+++++++++++/fY//{/////{////{///////////	Y 	Y 	Y 	Yr  c                     |dz  dz  }ddddddii}|j                  t        j                  |      d	
       g fd}t        j                  |d      5 }t        j                  |dddd      5  t        j                  |d|      5  | j                  d      |j                  _        |j                  ddd      }ddd       d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}
}	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}}y# 1 sw Y   xY w# 1 sw Y   xY w# 1 sw Y   xY w)'u3   봇 충돌로 거부 시 _cleanup_task가 호출됨r   r   rO   r  r  r   r  r  r   r   c                 (    j                  |        y r  r  r  s    r2   r  zMTestDevTeamBotConflict.test_conflict_calls_cleanup_task.<locals>.mock_cleanup	  r  rJ   r  r  r  r  r  r  r  r   rz  r  Fr  Nr   r  r`   rb   rc   rf   rg   r6   r  r   r  r   r  r   )r   r%   r   r   r  r  r  r  r<   rj   rk   rl   rm   rn   r   r   r   r   )rp   rZ   r:   r   r   r  r  r"  rq   rr   rs   rt   ru   r   r   r   r  s                   @r2    test_conflict_calls_cleanup_taskz7TestDevTeamBotConflict.test_conflict_calls_cleanup_task	  s   (+==
;)T[\

 	djj.A	+ LL|4	Y8@LLzF93UV	Y LLLQ	Y
 )-(B(B1(EHLL%!**;8JRW*XF	Y 	Y 	Y h*7*7****7******7*******>"'a'"a''''"a''''''s'''s''''''>'''>'''"'''a'''''''	Y 	Y 	Y 	Y 	Y 	Ys<   J2I65I) I6J)I3.I66J 	;JJc                    |dz  dz  }dddddii}|j                  t        j                  |      d	       t        j                  |d
      5 }t        j                  |dddd      5  | j                  d      |j                  _        |j                  ddd      }d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}	}g }d}	|d   }|	|v }|}|sd}|d   }||v }|}|st        j                  d|fd|	|f      t        j                  |	      t        j                  |      dz  }d d!|iz  }|j                  |       |s_t        j                  dfd"f      t        j                  |      t        j                  |      d#z  }d$d%|iz  }|j                  |       t        j                  |d&      i z  }d'd(|iz  }t        t        j                  |            dx}x}x}	x}x}x}x}}y# 1 sw Y   xY w# 1 sw Y   xY w))uZ   같은 팀(dev6-team)의 running 태스크는 봇 충돌이 아닌 팀 충돌로 처리됨r   r   rO   r"  rz  r   ro  r   r   r  r  r  r  r  r   r  Fr  Nr   r  r`   rb   rc   rf   rg   u
   같은 팀r  r   )z%(py3)s in %(py6)s)r   rg   z%(py8)sr   )z%(py11)s in %(py14)s)r   r%  r&  r  r6   r'  r(  )r   r%   r   r   r  r  r  r  r<   rj   rk   rl   rm   rn   r  r  )rp   rZ   r:   r   r   r  r"  rq   rr   rs   rt   ru   r   r   r   r   r@  rA  r   rB  rC  rD  rE  s                          r2   2test_same_team_running_not_treated_as_bot_conflictzITestDevTeamBotConflict.test_same_team_running_not_treated_as_bot_conflict	  s   (+==
+K

 	djj.A LL|4	Y8@LLzF93UV	Y )-(B(B1(EHLL%!**;8JRW*XF	Y 	Y h*7*7****7******7*******T|Tvi0T|00TKT6)CTTKCT4TTTT|0TTT|TTT0TTTTTTTKCTTTTKTTTCTTTTTTTTTTTTTTT	Y 	Y 	Y 	Ys$   I*)5II*I'	"I**I4r  )r   r   r   r   r  r  r  r  r  r  r  r  r  rQ   rJ   r2   r  r  >	  s3    h2,2*00&0&0*(4UrJ   r  c                   (    e Zd ZdZd Zd Zd Zd Zy)TestGetBusyBotsInfoExcludeuG   _get_busy_bots_info(exclude_task_id=...) 자기 자신 제외 테스트c                    |dz  dz  }dddddii}|j                  t        j                  |      d	       |j                  d
      }i }||k(  }|st	        j
                  d|fd||f      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}}y)uN   exclude_task_id로 자기 자신을 제외하면 결과에 포함되지 않음r   r   rO   r"  r^   r   ro  r   r   exclude_task_idr`   r   r"  r   r   r   Nr  rq  s
             r2   test_exclude_removes_own_entryz9TestGetBusyBotsInfoExclude.test_exclude_removes_own_entry	  s    (+==
+K

 	djj.A11,1Ov|vvvrJ   c                    |dz  dz  }dddddddd	d
i}|j                  t        j                  |      d       |j                  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   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)uF   exclude_task_id로 자기 자신만 제외, 다른 태스크는 유지r   r   rO   r^   r   ro  r  rp  r  )r"  r.  r   r   r"  r
  r   r  r"  r	  r   r   Nr   r.  r`   rb   rc   rf   rg   r  r  s                r2    test_exclude_keeps_other_entriesz;TestGetBusyBotsInfoExclude.test_exclude_keeps_other_entries
  s   (+==
*5K*5SZ[
 	djj.A11,1O w&    w&   w      &   &       gy)9\9)\9999)\999)999\9999999rJ   c                    |dz  dz  }dddddii}|j                  t        j                  |      d	       |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}}y)uQ   exclude_task_id=None이면 모든 running 태스크 반환 (기존 동작 호환)r   r   rO   r"  r^   r   ro  r   r   ri  r   r  r"  r	  r   r   Nr  r  s
             r2   test_exclude_none_returns_allz8TestGetBusyBotsInfoExclude.test_exclude_none_returns_all
  s    (+==
+K

 	djj.A113 w&    w&   w      &   &       rJ   c                    |dz  dz  }dddddddd	d
i}|j                  t        j                  |      d       |j                  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   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}
||
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\   핵심 버그 시나리오: dev팀 자신의 entry가 composite entry를 덮어쓰지 않음r   r   rO   r  r   rp  r  ry   ro  task-1241.1task-1242.1r   r   r  r
  r   r  r"  r	  r   r   Nr   r  r`   rb   rc   rf   rg   r   r  r  s                r2   2test_exclude_prevents_overwrite_of_composite_entryzMTestGetBusyBotsInfoExclude.test_exclude_prevents_overwrite_of_composite_entry
  sl   (+==
 +6)T[\+6)L
 	djj.A11-1P w&    w&   w      &   &       gy):]:)]::::)]:::):::]:::::::gy)8[8)[8888)[888)888[8888888rJ   N)r   r   r   r   r  r  r  r  rQ   rJ   r2   r  r  	  s    Q
:
!9rJ   r  c                   *    e Zd ZdZddZd Zd Zd Zy)TestBotConflictWithTimerEntryuY   dev팀 dispatch 시 이미 timer가 시작된 상태에서도 composite 봇 충돌 감지Nc                 v    t               }||_        ||nt        j                  ddi      |_        ||_        |S r  r  r  s        r2   r  z3TestBotConflictWithTimerEntry._make_mock_subprocess5
  r  rJ   c                    |dz  dz  }dddddddd	d
di}|j                  t        j                  |      d       t        j                  |d      5 }t        j                  |dddd      5  | j                  d      |j                  _        |j                  dddd      }d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    }||v }	|	slt        j                  d!|	fd"||f      t        j                  |      t        j                  |      dz  }
dd|
iz  }t        t        j                  |            dx}x}	}d#}|d    }||v }	|	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# 1 sw Y   xY w)$uo   핵심 회귀 테스트: composite가 bot-c 점유 + dev2-team timer entry가 이미 존재해도 충돌 감지r   r   rO   r  r   rp  r  ry   r  r  r  r   r   r  r  r  r  r  r   r  r  Fr   r  Nr   r  r`   rb   rc   rf   rg   r  r   r  r  r*  r  s               r2   8test_composite_conflict_detected_despite_own_timer_entryzVTestBotConflictWithTimerEntry.test_composite_conflict_detected_despite_own_timer_entry<
  s   (+==
 +6)T[\+6)\gh
 	djj.A LL|4	p8@LLzF93UV	p )-(B(B1(EHLL%!**;8JTain*oF	p 	p h*7*7****7******7*******+&++w+++++w++++w+++++++++++1y 11} 11111} 1111}111 11111111	p 	p 	p 	ps$   I/6H4%I4H>	9IIc                    |dz  dz  }dddddddd	d
i}|j                  t        j                  |      d       t        j                  |d      5 }t        j                  |dddd      5  | j                  d      |j                  _        |j                  dddd      }d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   }||v }	|	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# 1 sw Y   xY w)"u9   marketing이 bot-c 점유 + dev2-team dispatch 시 차단r   r   rO   r  r   rp  r  ry   ro  )r   
task-501.1r   r   r  r  r  r  r  r   r  r  Fr  Nr   r  r`   rb   rc   rf   rg   r  r   r  r*  r  s               r2   'test_dynamic_bot_on_dev_team_bot_blockszETestBotConflictWithTimerEntry.test_dynamic_bot_on_dev_team_bot_blocksS
  s   (+==
*5SZ[*5K
 	djj.A LL|4	o8@LLzF93UV	o )-(B(B1(EHLL%!**;8JT`hm*nF	o 	o h*7*7****7******7*******+&++w+++++w++++w+++++++++++	o 	o 	o 	os$   G.6F4$G4F>	9GGc           	         |dz  dz  }dddddddd	d
i}|j                  t        j                  |      d       t        j                  |d      5 }t        j                  |dddd      5  | j                  dt        j                  ddi            |j                  _        |j                  dddd      }d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# 1 sw Y   xY w# 1 sw Y   xY w) uK   force=True면 timer entry 존재해도 composite 충돌 무시하고 진행r   r   rO   r  r   rp  r  ry   ro  r  r   r   r  r  r  r  r  r   r   r  r&  r  Tr  Nr  r`   rb   rc   rf   rg   r*  r  s               r2   -test_force_bypasses_conflict_with_timer_entryzKTestBotConflictWithTimerEntry.test_force_bypasses_conflict_with_timer_entryh
  sF   (+==
+6)T[\+6)L
 	djj.A LL|4	l8@LLzF93UV	l )-(B(B1djjRZ\`QaFb(cHLL%!**;Q^fj*kF	l 	l h/</<////<//////<///////	l 	l 	l 	ls%   E.AE:EE	EE r  )r   r   r   r   r  r  r  r   rQ   rJ   r2   r  r  2
  s    c2.,*0rJ   r  c                   (    e Zd ZdZd Zd Zd Zd Zy)TestGetAvailableBotsWithTeamsuN   _get_available_bots_with_teams() — 가용 봇 목록 반환 헬퍼 테스트c                    |j                  i       }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 cg c]  }|d
   	 }	}dD ]  }
|
|	v }|st        j                  d|fd|
|	f      dt	        j
                         v st        j                  |
      rt        j                  |
      nddt	        j
                         v st        j                  |	      rt        j                  |	      nddz  }dd|iz  }t        t        j                  |            d	} y	c c}w )u3   busy_bots가 빈 dict일 때 8개 봇 전부 반환   r`   r  r   r"  r   r  r   Nbot_idri  rp  ru  bot-ebot-fr  bot-hbot-ir   z%(py0)s in %(py2)sr  bot_idsr/  r0  re   
_get_available_bots_with_teamsr   rj   rk   r   r   r   rl   rm   rn   )rp   rZ   r"  rs   r   r   ru   r   r  r,  r  r   r5  rt   s                 r2   &test_all_bots_available_when_none_busyzDTestGetAvailableBotsWithTeams.test_all_bots_available_when_none_busy
  s0   <<R@6{a{a{ass66{a067u5?77[ 	"C'>!!!3'!!!!!!3!!!3!!!!!!'!!!'!!!!!!!	" 8s   &H
c                    ddddddd}|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 	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D ]  }||
v }|st        j                  d|fd||
f      dt	        j
                         v st        j                  |      rt        j                  |      nddt	        j
                         v st        j                  |
      rt        j                  |
      nddz  }dd|iz  }t        t        j                  |            d} yc c}	w )u4   bot-b, bot-g가 busy일 때 나머지 6개만 반환r  r   r   r   r   )ri  r     r`   r  r   r"  r   r  r   Nr%  ri  rc  r  r,  r	  r   r   r  )rp  ru  r'  r(  r)  r*  r   r+  r  r/  r0  re   r-  )rp   rZ   	busy_botsr"  rs   r   r   ru   r   r  r,  rq   r   r   r  r   r5  rt   s                     r2   test_excludes_busy_botsz5TestGetAvailableBotsWithTeams.test_excludes_busy_bots
  s    "-D!,D
	 <<YG6{a{a{ass66{a067u5?77%wg%%%%wg%%%w%%%%%%g%%%g%%%%%%%%wg%%%%wg%%%w%%%%%%g%%%g%%%%%%%I 	"C'>!!!3'!!!!!!3!!!3!!!!!!'!!!'!!!!!!!	" 8s   1Mc           
         dddddddddddddddddddd	ddd
dd}|j                  |      }g }||k(  }|st        j                  d|fd||f      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}}y)u.   모든 봇이 busy일 때 빈 리스트 반환r  r   r1  r   r   r~  r  r  r  r  r&  r`   r   r"  r   r   r   N)	r.  rj   rk   r   r   r   rl   rm   rn   )rp   rZ   r3  r"  rs   r   r   r   s           r2    test_all_bots_busy_returns_emptyz>TestGetAvailableBotsWithTeams.test_all_bots_busy_returns_empty
  s     "-D!,D!,D!,D!,D!,D!,D!,D	
	 <<YGv|vvvrJ   c           	      Z   ddddddddd	}|j                  i       }|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=   각 봇의 default_team이 TEAM_TO_BOT_ID 역매핑과 일치r^   ry   r}   rx  ry  rz  r{  r|  r&  r%  default_teamr`   rb   rc   rf   rg   N)r.  rj   rk   rl   rm   rn   )rp   rZ   expected_mappingr"  r  r%  rq   rr   rs   rt   ru   s              r2   )test_returns_correct_default_team_mappingzGTestGetAvailableBotsWithTeams.test_returns_correct_default_team_mapping
  s     !       	
 <<R@ 	EE8_F(D,<V,DD(,DDDDD(,DDDD(DDD,DDDDDDDD	ErJ   N)r   r   r   r   r/  r4  r6  r:  rQ   rJ   r2   r"  r"  
  s    X""ErJ   r"  c                   0    e Zd ZdZddZd Zd Zd Zd Zy)	TestBotConflictAvailableBotsuJ   봇 충돌 에러 응답에 가용 봇 추천이 포함되는지 테스트Nc                 v    t               }||_        ||nt        j                  ddi      |_        ||_        |S r  r  r  s        r2   r  z2TestBotConflictAvailableBots._make_mock_subprocess
  r  rJ   c                    |dz  dz  }ddddddii}|j                  t        j                  |      d	
       t        j                  |d      5 }t        j                  |dddd      5  | j                  d      |j                  _        |j                  ddd      }d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}||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   8xY w# 1 sw Y   =xY w)$uZ   composite가 bot-g 점유 중 → dev6-team dispatch error에 available_bots 필드 존재r   r   rO   r  r  r   r  r  r   r   r  r  r  r  r  r   rz  r  Fr  Nr   r  r`   rb   rc   rf   rg   available_botsr   r  r"  r	  r   r   )r   r%   r   r   r  r  r  r  r<   rj   rk   rl   rm   rn   r   r   r   )rp   rZ   r:   r   r   r  r"  rq   rr   rs   rt   ru   r   r   s                 r2   1test_conflict_error_includes_available_bots_fieldzNTestBotConflictAvailableBots.test_conflict_error_includes_available_bots_field
  s   (+==
;)T[\

 	djj.A LL|4	Y8@LLzF93UV	Y )-(B(B1(EHLL%!**;8JRW*XF	Y 	Y h*7*7****7******7*******)6))))6)))))))))6)))6)))))))	Y 	Y 	Y 	Ys$   G"*5GG"G	G""G,c                    |dz  dz  }ddddddii}|j                  t        j                  |      d	
       t        j                  |d      5 }t        j                  |dddd      5  | j                  d      |j                  _        |j                  ddd      }d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   }||v }	|	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# 1 sw Y   xY w)!uJ   에러 메시지에 '가용 대안:' 문자열과 가용 봇 정보 포함r   r   rO   r  r  r   r  r  r   r   r  r  r  r  r  r   rz  r  Fr  Nr   r  r`   rb   rc   rf   rg   u   가용 대안:r  r   r  r*  r  s               r2   1test_conflict_error_message_includes_alternativeszNTestBotConflictAvailableBots.test_conflict_error_message_includes_alternatives
  s   (+==
;)T[\

 	djj.A LL|4	Y8@LLzF93UV	Y )-(B(B1(EHLL%!**;8JRW*XF	Y 	Y h*7*7****7******7*******46)#44#44444#4444444#44444444	Y 	Y 	Y 	Ys$   F<*5F/F</F9	4F<<Gc                 ,   |dz  dz  }ddddddii}|j                  t        j                  |      d	
       t        j                  |d      5 }t        j                  |dddd      5  | j                  d      |j                  _        |j                  ddd      }d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}	}|j                  dg       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}}	y# 1 sw Y   ZxY w# 1 sw Y   _xY wc c}w )%uA   available_bots에 점유 중인 봇(bot-g)이 포함되지 않음r   r   rO   r  r  r   r  r  r   r   r  r  r  r  r  r   rz  r  Fr  Nr   r  r`   rb   rc   rf   rg   r?  r%  rc  r  available_bot_idsr	  r   r   )r   r%   r   r   r  r  r  r  r<   rj   rk   rl   rm   rn   r!   r   r   r   )rp   rZ   r:   r   r   r  r"  rq   rr   rs   rt   ru   r  rD  r   r   s                   r2   0test_conflict_error_available_bots_excludes_busyzMTestBotConflictAvailableBots.test_conflict_error_available_bots_excludes_busy
  s   (+==
;)T[\

 	djj.A LL|4	Y8@LLzF93UV	Y )-(B(B1(EHLL%!**;8JRW*XF	Y 	Y h*7*7****7******7*******:@**EUWY:Z[U8_[[/w/////w////w//////////////////	Y 	Y 	Y 	Y \s*   H*5G7HH7H	<HHc                    |dz  dz  }dddddddddddd	dddd
ddddddddddddddddddi}|j                  t        j                  |      d       t        j                  |d      5 }t        j                  |dddd      5  | j                  d      |j                  _        |j                  ddd      }d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%   }||v }	|	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# 1 sw Y   xY w)(u>   모든 봇이 busy일 때 '모든 봇이 작업 중' 메시지r   r   rO   r  r   ri  r  rp  ru  r'  r(  r  r)  r*  r}  r   r   r  r  r  r  r  r   rz  r  Fr  Nr   r  r`   rb   rc   rf   rg   r  r  r   r  r*  r  s               r2   #test_conflict_all_bots_busy_messagez@TestBotConflictAvailableBots.test_conflict_all_bots_busy_message  s   (+==
(3yQXY(3yQXY(3yQXY(3yQXY(3yQXY(3yQXY(3yQXY(3yQXY	
 	djj.A LL|4	Y8@LLzF93UV	Y )-(B(B1(EHLL%!**;8JRW*XF	Y 	Y h*7*7****7******7*******)>VI->>)->>>>>)->>>>)>>>->>>>>>>>	Y 	Y 	Y 	Ys$   2G5GGG	GG)r  )	r   r   r   r   r  r@  rB  rE  rG  rQ   rJ   r2   r<  r<  
  s    T*(5(0*?rJ   r<  c                   @    e Zd ZdZd Zd Zd Zd Zd Zd Z	d Z
d	 Zy
)TestImageQcGateBlocku]   이미지/광고 작업 시 --workflow 미적용 시 차단 + --skip-qc-gate 우회 테스트c                    |j                  t        dg 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)uE   이미지 키워드 포함 + --workflow 없음 → sys.exit(1) 호출r  )r  r  r^   r     배너 이미지 생성Nr6   r`   rX  rY  rZ  r>  r   r   r>   r  r\  r]  r  r^  r_  rj   rk   r   r   r   rl   rm   rn   
rp   rZ   r  rY  r   rr   r   r   r?  r   s
             r2   )test_image_keyword_without_workflow_exitsz>TestImageQcGateBlock.test_image_keyword_without_workflow_exits,  s    C)tu]]:& 	 (	 ~~'~""'a'"a''''"a''''''x'''x'''~'''"'''a'''''''	  	    D77E c                 <   t               }d|_        t        j                  ddi      |_        d|_        |j                  t        dg d       t        j                  |d      5 }t        j                  |dd	d
d      5  ||j                  _        |j                          ddd       ddd       |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}}	y# 1 sw Y   xY w# 1 sw Y   xY w)uO   이미지 키워드 + --skip-qc-gate → 차단 없이 통과 (dispatch 진행)r   r   r  r   r  )r  r  r^   r  rK  z--skip-qc-gater  r  r  r  r  Nr   r  r  r	  r   r   r   r  r%   r   r  r  r   r>   r   r  r  r  r  r  r   r  rj   rk   rl   r   r   r   rm   rn   r  s               r2   +test_image_keyword_with_skip_qc_gate_passesz@TestImageQcGateBlock.test_image_keyword_with_skip_qc_gate_passes3  s-   k!"!ZZ4(89i	
 LL|4	 8@LLzF93UV	  )4HLL%	  	  $$&HLL)!x6!!!!x6!!!x!!!!!!6!!!6!!!!!!!	  	  	  	 $   $F?"F!FF	FFc                    t               }d|_        t        j                  ddi      |_        d|_        |j                  t        dg d       t        j                  |d      5 }t        j                  |dd	d
d      5  t        dd      5  ||j                  _        |j                          ddd       ddd       ddd       |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}}	y# 1 sw Y   xY w# 1 sw Y   xY w# 1 sw Y   xY w)uG   이미지 키워드 + --workflow image-qc-gate → 차단 없이 통과r   r   r  r   r  )r  r  r^   r  rK  z
--workflowzimage-qc-gater  r  r  r  r  z5prompts.image_workflow.build_workflow_overview_promptzworkflow promptr.  Nr   r  r  r	  r   r   rQ  r  s               r2   'test_image_keyword_with_workflow_passesz<TestImageQcGateBlock.test_image_keyword_with_workflow_passesK  sR   k!"!ZZ4(89	
 LL|4	 8@LLzF93UV	  IXij	 
 )4HLL%	  	  	  $$&HLL)!x6!!!!x6!!!x!!!!!!6!!!6!!!!!!!	  	  	  	  	  	 s<   $F4?F("F/F(7F4F%!F((F1	-F44F=c                 <   t               }d|_        t        j                  ddi      |_        d|_        |j                  t        dg d       t        j                  |d      5 }t        j                  |dd	d
d      5  ||j                  _        |j                          ddd       ddd       |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}}	y# 1 sw Y   xY w# 1 sw Y   xY w)uP   이미지 키워드 없음 + --workflow 없음 → 정상 통과 (영향 없음)r   r   r  r   r  )r  r  r^   r  u   API 엔드포인트 구현r  r  r  r  r  Nr   r  r  r	  r   r   rQ  r  s               r2   -test_no_image_keyword_without_workflow_passeszBTestImageQcGateBlock.test_no_image_keyword_without_workflow_passesl  s-   k!"!ZZ4(89Z	
 LL|4	 8@LLzF93UV	  )4HLL%	  	  $$&HLL)!x6!!!!x6!!!x!!!!!!6!!!6!!!!!!!	  	  	  	 rS  c                    |j                  t        dg 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)u#   영문 'banner' 키워드도 차단r  )r  r  r^   r  zCreate banner designNr6   r`   rX  rY  rZ  r>  r   rL  rM  s
             r2   "test_english_banner_keyword_blocksz7TestImageQcGateBlock.test_english_banner_keyword_blocks  s    C)qr]]:& 	 (	 ~~'~""'a'"a''''"a''''''x'''x'''~'''"'''a'''''''	  	 rO  c                    |j                  t        dg 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)u"   영문 'image' 키워드도 차단r  )r  r  r^   r  zGenerate product imageNr6   r`   rX  rY  rZ  r>  r   rL  rM  s
             r2   !test_english_image_keyword_blocksz6TestImageQcGateBlock.test_english_image_keyword_blocks  s    C)st]]:& 	 (	 ~~'~""'a'"a''''"a''''''x'''x'''~'''"'''a'''''''	  	 rO  c                    |j                  t        dg 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)u&   한국어 '광고' 키워드도 차단r  )r  r  r^   r  u   메타 광고 소재 제작Nr6   r`   rX  rY  rZ  r>  r   rL  rM  s
             r2   test_korean_ad_keyword_blocksz2TestImageQcGateBlock.test_korean_ad_keyword_blocks  s    C)xy]]:& 	 (	 ~~'~""'a'"a''''"a''''''x'''x'''~'''"'''a'''''''	  	 rO  c                    |j                  t        dg 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)u)   한국어 '디자인' 키워드도 차단r  )r  r  r^   r  u   UI 디자인 작업Nr6   r`   rX  rY  rZ  r>  r   rL  rM  s
             r2   !test_korean_design_keyword_blocksz6TestImageQcGateBlock.test_korean_design_keyword_blocks  s    C)pq]]:& 	 (	 ~~'~""'a'"a''''"a''''''x'''x'''~'''"'''a'''''''	  	 rO  N)r   r   r   r   rN  rR  rU  rW  rY  r[  r]  r_  rQ   rJ   r2   rI  rI  )  s.    g("0"B"0((((rJ   rI  c                   6    e Zd ZdZd	dZd Zd Zd Zd Zd Z	y)
TestLogicalTeamBotConflictug   논리적팀(design/marketing/content)이 봇 점유 시 해당 봇의 dev팀 dispatch 차단 테스트Nc                 v    t               }||_        ||nt        j                  ddi      |_        ||_        |S r  r  r  s        r2   r  z0TestLogicalTeamBotConflict._make_mock_subprocess  r  rJ   c                    |dz  dz  }ddddddii}|j                  t        j                  |      d	
       t        j                  |d      5 }t        j                  |dddd      5  | j                  d      |j                  _        |j                  ddd      }d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   }||v }	|	slt        j                  d|	fd||f      t        j                  |      t        j                  |      dz  }
dd|
iz  }t        t        j                  |            dx}x}	}d}|d   }||v }	|	slt        j                  d|	fd||f      t        j                  |      t        j                  |      dz  }
dd|
iz  }t        t        j                  |            dx}x}	}d}|d   }||v }	|	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# 1 sw Y   xY w) u;   design이 bot-e 점유 중 → dev4-team dispatch 시 errorr   r   rO   task-1400.1rJ  r   r'  r  r   r   r  r  r  r  r  r  r   rx  r  Fr  Nr   r  r`   rb   rc   rf   rg   r  r   r  r*  r  s               r2   )test_design_on_bot_e_blocks_dev4_dispatchzDTestLogicalTeamBotConflict.test_design_on_bot_e_blocks_dev4_dispatch  s"   (+==
8yQXY

 	djj.A LL|4	Y8@LLzF93UV	Y )-(B(B1(EHLL%!**;8JRW*XF	Y 	Y h*7*7****7******7*******+&++w+++++w++++w+++++++++++,6),,x,,,,,x,,,,x,,,,,,,,,,,1y 11} 11111} 1111}111 11111111	Y 	Y 	Y 	Yr  c                    |dz  dz  }ddddddii}|j                  t        j                  |      d	
       t        j                  |d      5 }t        j                  |dddd      5  | j                  d      |j                  _        |j                  ddd      }d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   }||v }	|	slt        j                  d|	fd||f      t        j                  |      t        j                  |      dz  }
dd|
iz  }t        t        j                  |            dx}x}	}d}|d   }||v }	|	slt        j                  d|	fd||f      t        j                  |      t        j                  |      dz  }
dd|
iz  }t        t        j                  |            dx}x}	}d}|d   }||v }	|	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# 1 sw Y   xY w) u<   content가 bot-b 점유 중 → dev1-team dispatch 시 errorr   r   rO   ztask-1380.1contentr   ri  r  r   r   r  r  r  r  r  r   r^   r  Fr  Nr   r  r`   rb   rc   rf   rg   r  r   r  r*  r  s               r2   *test_content_on_bot_b_blocks_dev1_dispatchzETestLogicalTeamBotConflict.test_content_on_bot_b_blocks_dev1_dispatch  s"   (+==
9	RYZ

 	djj.A LL|4	Y8@LLzF93UV	Y )-(B(B1(EHLL%!**;8JRW*XF	Y 	Y h*7*7****7******7*******+&++w+++++w++++w+++++++++++-F9--y-----y----y-----------1y 11} 11111} 1111}111 11111111	Y 	Y 	Y 	Yr  c           	         |dz  dz  }ddddddii}|j                  t        j                  |      d	
       t        j                  |d      5 }t        j                  |dddd      5  | j                  dt        j                  ddi            |j                  _        |j                  dd      }d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# 1 sw Y   xY w# 1 sw Y   xY w)uF   design이 completed면 봇 점유 아님 → dev4-team 정상 dispatchr   r   rO   rd  rJ  r   r'  r  r   r   r  r  r  r  re  r   r   r  rx  r  Nr  r`   rb   rc   rf   rg   r*  r  s               r2   *test_design_completed_allows_dev4_dispatchzETestLogicalTeamBotConflict.test_design_completed_allows_dev4_dispatch  s7   (+==
8{SZ[

 	djj.A LL|4	I8@LLzF93UV	I )-(B(B1djjRZ\`QaFb(cHLL%!**;HF	I 	I h/</<////<//////<///////	I 	I 	I 	Ir  c           	         |dz  dz  }dddddii}|j                  t        j                  |      d	       t        j                  |d
      5 }t        j                  |dddd      5  | j                  dt        j                  ddi            |j                  _        |j                  dd      }d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# 1 sw Y   xY w# 1 sw Y   xY w)ub   논리적팀이 running이지만 bot 필드 없음 → dev팀 정상 dispatch (false block 없음)r   r   rO   rd  rJ  r   ro  r   r   r  r  r  r  r  r   r   r  r^   r  Nr  r`   rb   rc   rf   rg   r*  r  s               r2   2test_logical_team_without_bot_field_no_false_blockzMTestLogicalTeamBotConflict.test_logical_team_without_bot_field_no_false_block  s4   (+==
8yI

 	djj.A LL|4	I8@LLzF93UV	I )-(B(B1djjRZ\`QaFb(cHLL%!**;HF	I 	I h/</<////<//////<///////	I 	I 	I 	Is%   E)A	E2EE	EEc           	         |dz  dz  }dddddddd	dd
i}|j                  t        j                  |      d       t        j                  |d      5 }t        j                  |dddd      5  | j                  dt        j                  ddi            |j                  _        |j                  dd      }d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# 1 sw Y   xY w# 1 sw Y   xY w)ua   여러 논리적팀이 각각 다른 봇 점유 → 해당 dev팀만 차단, 다른 팀은 정상r   r   rO   rJ  r   r'  r  r  ri  rt  r   r   r  r  r  r  r  r   r   r  r}   r  Nr  r`   rb   rc   rf   rg   r*  r  s               r2   *test_multiple_logical_teams_different_botszETestLogicalTeamBotConflict.test_multiple_logical_teams_different_bots  s?   (+==
(0IgV(3yQXY
 	djj.A LL|4	I8@LLzF93UV	I )-(B(B1djjRZ\`QaFb(cHLL%!**;HF	I 	I h/</<////<//////<///////	I 	I 	I 	Is%   E/A	E	8E	E	EEr  )
r   r   r   r   r  rf  ri  rk  rm  ro  rQ   rJ   r2   ra  ra    s#    q2,2,0&0&0rJ   ra  c                   (    e Zd ZdZd Zd Zd Zd Zy)TestSelectAndReserveBotuA   _select_and_reserve_bot() 원자적 봇 선택 + 예약 테스트c                    |dz  dz  }dddddii}|j                  t        j                  |      d	       |j                  d      }d
}||k(  }|st	        j
                  d|fd||f      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}}t        j                  |j                  d	            }
|
d   d   }|j                  }d} ||      }d
}||k(  }|st	        j
                  d|fd||f      t	        j                  |      t	        j                  |      t	        j                  |      t	        j                  |      t	        j                  |      dz  }dd|iz  }t        t	        j                  |            dx}x}x}x}x}}y)uO   빈 timers → bot-b(첫 번째 봇) 선택 + timer에 bot 필드 기록 확인r   r   rO   task-test.1r^   r   ro  r   r   ri  r`   r   selectedr   r   r   Nr  rR  rS  r  rV  )r   r%   r   _select_and_reserve_botrj   rk   r   r   r   rl   rm   rn   r   r   r!   )rp   rZ   r:   r   r   rt  rs   r   r   r   writtenrq   r   r   r  r   rZ  r[  s                     r2    test_selects_first_available_botz8TestSelectAndReserveBot.test_selects_first_available_bot  si   (+==
;)L

 	djj.A77F""x7""""x7""""""x"""x"""7""""""" **Z1171CDw.D.22D5D259DWD9WDDDD9WDDD.DDD2DDD5DDD9DDDWDDDDDDDDrJ   c                    |dz  dz  }ddddddddddd	i}|j                  t        j                  |      d
       |j                  d      }d}||k(  }|st	        j
                  d|fd||f      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}}y)u1   dev1(bot-b), dev2(bot-c) running → bot-d 선택r   r   rO   r^   r   ro  ry   r}   )ztask-other1.1ztask-other2.1rs  r   r   rs  ru  r`   r   rt  r   r   r   N)r   r%   r   ru  rj   rk   r   r   r   rl   rm   rn   )
rp   rZ   r:   r   r   rt  rs   r   r   r   s
             r2   test_skips_busy_botsz,TestSelectAndReserveBot.test_skips_busy_bots1  s    (+==
-8I!N-8I!N+6)L
 	djj.A77F""x7""""x7""""""x"""x"""7"""""""rJ   c                    |dz  dz  }ddddddii}|j                  t        j                  |      d	
       |j                  d      }t        j                  |j                  d	
            }|d   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   }||k(  }	|	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)uj   봇 선택 후 task-timers.json을 다시 읽어서 해당 task의 bot 필드가 설정되었는지 확인r   r   rO   ztask-reserve.1r^   r   u   예약 테스트r  r   r   r  r   r  
task_entryr	  r   r   Nr`   r  rt  )r   r%   r   ru  r   r   rj   rk   rl   r   r   r   rm   rn   )rp   rZ   r:   r   r   rt  rv  r{  rq   rs   r   r   s               r2   test_reserves_bot_in_timer_filez7TestSelectAndReserveBot.test_reserves_bot_in_timer_fileA  sE   (+==
 kY_q"r

 	djj.A778HI **Z1171CDW%&67
"u
""""u
"""u""""""
"""
"""""""% , H,,,, H,,, ,,,,,,H,,,H,,,,,,,rJ   c                 0   |dz  dz  }ddddddddddd	ddd
ddddddddddddddd	i}|j                  t        j                  |      d       t        j                  t
              5  |j                  d       ddd       y# 1 sw Y   yxY w)u*   모든 봇이 busy → RuntimeError 발생r   r   rO   r^   r   ro  ry   r}   rx  ry  rz  r{  r|  r  )	r   r   r   r~  r  r  r  r  rs  r   r   rs  N)r   r%   r   r  r\  rn  ru  r  s        r2   test_raises_when_all_busyz1TestSelectAndReserveBot.test_raises_when_all_busyS  s    (+==
(3yI(3yI(3yI(3yI(3yI(3yI(3yI(3yI+6)L

 	djj.A]]<( 	@00?	@ 	@ 	@s   1BBN)r   r   r   r   rw  ry  r|  r~  rQ   rJ   r2   rq  rq    s    KE$# -$@rJ   rq  c                   X    e Zd ZdZddeddfdZ	 ddededdfdZd	 Zd
 Zd Z	d Z
d Zy)TestValidateModelConsistencyu.   _validate_model_consistency() 단위 테스트	org_modelr   r   c                     |dz  }dddddd|dd	gd
dddddgiii}|j                  t        j                  |      d       |S )Nzorg-structure.jsonr   r   r   r   r}   dagda)r  model)r   r  )r   r   r7   	aphroditeclaude-opus-4-6)r   r  r   r   r  )rp   r:   r  org_filer   s        r2   _make_org_fixturez.TestValidateModelConsistency._make_org_fixtureq  s    22'; 4?3:Y,O!"* (8+6AR$S
* 	DJJt,w?rJ   	bot_modelkey_hashc                 x    |dz  }|dt         |iddi}|j                  t        j                  |      d       |S )Nbot_settings.jsondev3_Dagda_botfake-token-12345display_namemodelstokenr   r   )_TEST_CHAT_IDr   r%   r   )rp   r:   r  r  settings_filer   s         r2   _make_bot_settings_fixturez7TestValidateModelConsistency._make_bot_settings_fixture  sP     !#66 0()4+
 	  D!1G DrJ   c                    | j                  d      }| j                  d      |j                  d|       |j                  dddi       |j                  ddd	i       |j                  d
t               |j                  dfd       ddl} G fdd       G d d      |j
                  j                  }|j                  |j
                  dt        fd             ddl}ddl	}d|j                  v r|j                  d= t        t        j                  j                  dd            }t        |      |j                  vr%|j                  j!                  dt        |             ddl}	|j                  |	d|       |j                  |	dddi       |j                  |	ddd	i       |j                  |	dt               |	j%                  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}||k(  }|slt'        j(                  d$|fd%||f      t'        j*                  |      t'        j*                  |      d z  }d!d"|iz  }t-        t'        j.                  |            dx}x}}|j                  |j
                  dt        |             y)(u/   org와 bot 모델이 같을 때 consistent=Truer  r  r  zdispatch.ORG_FILEzdispatch.TEAM_BOTr}   r~   zdispatch.BOT_KEYS0b94683120a691cfzdispatch.CHAT_IDzdispatch.Pathc                  \    | dk(  rj                   S  t        d      j                  | i |S )NrQ   rv  )r   
__import__r   )r|  r}  r  s     r2   rS   zETestValidateModelConsistency.test_consistent_models.<locals>.<lambda>  s2    DBJM$8$8 LfJW`LaLfLfhlLwpvLw rJ   r   Nc                       e Zd Z fdZy)FTestValidateModelConsistency.test_consistent_models.<locals>._FakeHomec                            S r  rQ   rp   other_FakeCokacdirr:   s     r2   __truediv__zRTestValidateModelConsistency.test_consistent_models.<locals>._FakeHome.__truediv__      $X..rJ   Nr   r   r   r  r  r:   s   r2   	_FakeHomer        /rJ   r  c                       e Zd Zd Zd Zy)JTestValidateModelConsistency.test_consistent_models.<locals>._FakeCokacdirc                     || _         y r  _baserp   bases     r2   __init__zSTestValidateModelConsistency.test_consistent_models.<locals>._FakeCokacdir.__init__  	    !
rJ   c                      | j                   |z  S r  r  rp   r  s     r2   r  zVTestValidateModelConsistency.test_consistent_models.<locals>._FakeCokacdir.__truediv__      zzE))rJ   Nr   r   r   r  r  rQ   rJ   r2   r  r        "*rJ   r  homec                               S r  rQ   r  s   r2   rS   zETestValidateModelConsistency.test_consistent_models.<locals>.<lambda>  	    y{ rJ   r<   r   r   ORG_FILEri   r  CHAT_ID
consistentTrD  rF  rc   rf   rg   r  r`   rb   r  r   )r  r  r   r  rv  r   r  staticmethodr>  r>   rC   r   r    r!   r=   r?   r@   r<   _validate_model_consistencyrj   rk   rl   rm   rn   )rp   r:   r  r  rv  original_homer>  r>   r*   rZ   r"  rq   rr   rs   rt   ru   r  r  r  s    `              @@@r2   test_consistent_modelsz3TestValidateModelConsistency.test_consistent_models  s   ))(>O)P77L]7^/:/+v1FG/&:L1MN.>w	
 		/ 	/	* 	*  ))GLL&,?R2ST$J'(8:OPQ	y>)HHOOAs9~.'L*h?L*{F6KLL*v?Q6RSL)]C99+Fl#+t+#t++++#t+++#+++t+++++++k"7&77"&77777"&7777"777&77777777k"7&77"&77777"&7777"777&77777777i /K/ K//// K/// ///K///////GLL&,}2MNrJ   c                    ddl }ddl}| j                  d      }| j                  d      } G fdd       G d	 d
      |j                  j
                  }|j                  |j                  dt        fd             ddl}	t	        t        j                  j                  dd            }
t        |
      |	j                  vr%|	j                  j                  dt        |
             d|	j                  v r|	j                  d= ddl}|j                  |d|       |j                  |dddi       |j                  |dddi       |j                  |dt"               |j%                  |j&                        5  |j)                  d      }ddd       d   }d}||u }|slt+        j,                  d|fd||f      t+        j.                  |      t+        j.                  |      dz  }dd|iz  }t1        t+        j2                  |            dx}x}}|d   }d}||k(  }|slt+        j,                  d|fd ||f      t+        j.                  |      t+        j.                  |      dz  }dd|iz  }t1        t+        j2                  |            dx}x}}|d!   }d}||k(  }|slt+        j,                  d|fd ||f      t+        j.                  |      t+        j.                  |      dz  }dd|iz  }t1        t+        j2                  |            dx}x}}|j4                  D cg c](  }|j6                  |j&                  k(  s|j8                  * }}d" |D        }t;        |      }|st+        j<                  d#|       d$z   d%t?        j@                         v st+        jB                  t:              rt+        j.                  t:              nd%t+        j.                  |      t+        j.                  |      d&z  }t1        t+        j2                  |            dx}}|j                  |j                  dt        |             y# 1 sw Y   xY wc c}w )'uA   org와 bot 모델이 다를 때 consistent=False + WARNING 로그r   Nr  r  zclaude-sonnet-4-5r  c                       e Zd Z fdZy)HTestValidateModelConsistency.test_inconsistent_models.<locals>._FakeHomec                            S r  rQ   r  s     r2   r  zTTestValidateModelConsistency.test_inconsistent_models.<locals>._FakeHome.__truediv__  r  rJ   Nr  r  s   r2   r  r    r  rJ   r  c                       e Zd Zd Zd Zy)LTestValidateModelConsistency.test_inconsistent_models.<locals>._FakeCokacdirc                     || _         y r  r  r  s     r2   r  zUTestValidateModelConsistency.test_inconsistent_models.<locals>._FakeCokacdir.__init__  r  rJ   c                      | j                   |z  S r  r  r  s     r2   r  zXTestValidateModelConsistency.test_inconsistent_models.<locals>._FakeCokacdir.__truediv__  r  rJ   Nr  rQ   rJ   r2   r  r    r  rJ   r  r  c                               S r  rQ   r  s   r2   rS   zGTestValidateModelConsistency.test_inconsistent_models.<locals>.<lambda>  r  rJ   r   r   r<   r  ri   r}   r~   r  r  r  r  FrD  rF  rc   rf   rg   r  r`   rb   r  c              3   $   K   | ]  }d |v  
 yw)u   모델 불일치NrQ   rm  s     r2   rp  zHTestValidateModelConsistency.test_inconsistent_models.<locals>.<genexpr>  s      
*-#%
rq  uB   WARNING 로그에 '모델 불일치' 미포함. 기록된 로그: rr  rs  r   )"ru  rv  r  r  r   r  r   r  r>   r   r    r!   r=   r?   r@   rC   r<   r  rw  rx  r  rj   rk   rl   rm   rn   rz  r{  r  rs  r   r   r   r   )rp   r:   r  r|  ru  rv  r  r  r  r>   r*   rZ   r"  rq   rr   rs   rt   ru   rw  r~  r   r  r  s    `                   @@r2   test_inconsistent_modelsz5TestValidateModelConsistency.test_inconsistent_models  s   ))(>O)P77L_7`	/ 	/	* 	*  ))GLL&,?R2ST(8:OPQ	y>)HHOOAs9~.$J''L*h?L*{F6KLL*v?Q6RSL)]C__W__- 	K!==kJF	K l#,u,#u,,,,#u,,,#,,,u,,,,,,,k"7&77"&77777"&7777"777&77777777k"9&99"&99999"&9999"999&99999999/5~~^!gooA]AII^^
1A
 	cs 
 
 	c 
 	cQbQbOP`Oab	c 	c\b\b	c 	cJbJb  	c 	cYbYb  	c 	cYbYb
 	c 	cYbYb
 	c 	c 	cObOb	c 	c 	GLL&,}2MN	K 	K _s   ?Q&QQQc                    ddl }ddl}dz  }| j                        } G fdd       G d d      |j                  j                  }|j                  |j                  dt        fd	             t        t        j                  j                  d
d            }t        |      |j                  vr%|j                  j                  dt        |             d|j                  v r|j                  d= ddl}	|j                  |	d|       |j                  |	dddi       |j                  |	dddi       |j                  |	dt               |	j!                  d      }
t#        |
t$              }|sd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dt'        j(                         v st+        j,                  t$              rt+        j.                  t$              ndt+        j.                  |      dz  }t1        t+        j2                  |            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  }dd|iz  }t1        t+        j2                  |            dx}}|
d   }d}||k(  }|slt+        j4                  d |fd!||f      t+        j.                  |      t+        j.                  |      d"z  }d#d$|iz  }t1        t+        j2                  |            dx}x}}|
d%   }d&}||k(  }|slt+        j4                  d |fd!||f      t+        j.                  |      t+        j.                  |      d"z  }d#d$|iz  }t1        t+        j2                  |            dx}x}}|j                  |j                  dt        |             y)'u6   org-structure.json 없을 때 graceful (에러 안남)r   Nznonexistent-org.jsonc                       e Zd Z fdZy)ETestValidateModelConsistency.test_org_file_missing.<locals>._FakeHomec                            S r  rQ   r  s     r2   r  zQTestValidateModelConsistency.test_org_file_missing.<locals>._FakeHome.__truediv__  r  rJ   Nr  r  s   r2   r  r    r  rJ   r  c                       e Zd Zd Zd Zy)ITestValidateModelConsistency.test_org_file_missing.<locals>._FakeCokacdirc                     || _         y r  r  r  s     r2   r  zRTestValidateModelConsistency.test_org_file_missing.<locals>._FakeCokacdir.__init__  r  rJ   c                      | j                   |z  S r  r  r  s     r2   r  zUTestValidateModelConsistency.test_org_file_missing.<locals>._FakeCokacdir.__truediv__  r  rJ   Nr  rQ   rJ   r2   r  r    r  rJ   r  r  c                               S r  rQ   r  s   r2   rS   zDTestValidateModelConsistency.test_org_file_missing.<locals>.<lambda>  r  rJ   r   r   r<   r  ri   r}   r~   r  r  r  r   r!  r"  r=  r#  r  r   r  r	  r   r   r   r`   rb   rc   rf   rg   r  r   )rv  r>   r  r   r  r   r  r   r    r!   r=   r?   r@   rC   r<   r  r  r!  r=  r   r   rj   r   rl   rm   rn   rk   )rp   r:   r  rv  r>   missing_orgr  r  r*   rZ   r"  rr   rt   rq   rs   r   r   ru   r  r  s    `                @@r2   test_org_file_missingz2TestValidateModelConsistency.test_org_file_missing  s   !7777A	/ 	/	* 	*  ))GLL&,?R2ST(8:OPQ	y>)HHOOAs9~.$J''L*kBL*{F6KLL*v?Q6RSL)]C 99+F&$''''''''z'''z''''''&'''&''''''$'''$''''''''''%|v%%%%|v%%%|%%%%%%v%%%v%%%%%%%i /K/ K//// K/// ///K///////k"(b("b(((("b((("(((b(((((((GLL&,}2MNrJ   c                    ddl }ddl}| j                  |      }|dz  j                           G fdd       G d d      |j                  j
                  }|j                  |j                  dt        fd	             t	        t        j                  j                  d
d            }t        |      |j                  vr%|j                  j                  dt        |             d|j                  v r|j                  d= ddl}|j                  |d|       |j                  |dddi       |j                  |dddi       |j                  |dt                |j#                  d      }	t%        |	t&              }
|
sd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dt)        j*                         v st-        j.                  t&              rt-        j0                  t&              ndt-        j0                  |
      dz  }t3        t-        j4                  |            d}
d}||	v }|st-        j6                  d|fd||	f      t-        j0                  |      dt)        j*                         v st-        j.                  |	      rt-        j0                  |	      nddz  }dd|iz  }t3        t-        j4                  |            dx}}|	d   }d}
||
k(  }|slt-        j6                  d |fd!||
f      t-        j0                  |      t-        j0                  |
      d"z  }d#d$|iz  }t3        t-        j4                  |            dx}x}}
|	d%   }d&}
||
k(  }|slt-        j6                  d |fd!||
f      t-        j0                  |      t-        j0                  |
      d"z  }d#d$|iz  }t3        t-        j4                  |            dx}x}}
|j                  |j                  dt        |             y)'u5   bot_settings.json 없을 때 graceful (에러 안남)r   N
empty_homec                       e Zd Z fdZy)ITestValidateModelConsistency.test_bot_settings_missing.<locals>._FakeHomec                            S r  rQ   )rp   r  r  	empty_dirs     r2   r  zUTestValidateModelConsistency.test_bot_settings_missing.<locals>._FakeHome.__truediv__>  s    $Y//rJ   Nr  )r  r  s   r2   r  r  =  s    0rJ   r  c                       e Zd Zd Zd Zy)MTestValidateModelConsistency.test_bot_settings_missing.<locals>._FakeCokacdirc                     || _         y r  r  r  s     r2   r  zVTestValidateModelConsistency.test_bot_settings_missing.<locals>._FakeCokacdir.__init__B  r  rJ   c                      | j                   |z  S r  r  r  s     r2   r  zYTestValidateModelConsistency.test_bot_settings_missing.<locals>._FakeCokacdir.__truediv__E  r  rJ   Nr  rQ   rJ   r2   r  r  A  r  rJ   r  r  c                               S r  rQ   r  s   r2   rS   zHTestValidateModelConsistency.test_bot_settings_missing.<locals>.<lambda>I  r  rJ   r   r   r<   r  ri   r}   r~   r  r  r  r   r!  r"  r=  r#  r  r   r  r	  r   r   r   r`   rb   rc   rf   rg   r  r   )rv  r>   r  rT   r   r  r   r  r   r    r!   r=   r?   r@   rC   r<   r  r  r!  r=  r   r   rj   r   rl   rm   rn   rk   )rp   r:   r  rv  r>   r  r  r*   rZ   r"  rr   rt   rq   rs   r   r   ru   r  r  r  s                    @@@r2   test_bot_settings_missingz6TestValidateModelConsistency.test_bot_settings_missing3  s   ))(3|+		0 	0	* 	*  ))GLL&,?R2ST(8:OPQ	y>)HHOOAs9~.$J''L*h?L*{F6KLL*v?Q6RSL)]C 99+F&$''''''''z'''z''''''&'''&''''''$'''$''''''''''%|v%%%%|v%%%|%%%%%%v%%%v%%%%%%%i /K/ K//// K/// ///K///////k"(b("b(((("b((("(((b(((((((GLL&,}2MNrJ   c                 f	   ddl }ddl}| j                        }| j                        } G fdd       G d d      |j                  j
                  }|j                  |j                  dt        fd             t	        t        j                  j                  d	d
            }t        |      |j                  vr%|j                  j                  dt        |             d|j                  v r|j                  d= ddl}	|j                  |	d|       |j                  |	dddi       |j                  |	dddi       |j                  |	dt                |	j#                  d      }
t%        |
t&              }|sd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dt)        j*                         v st-        j.                  t&              rt-        j0                  t&              ndt-        j0                  |      dz  }t3        t-        j4                  |            d}|
d   }d}||k(  }|slt-        j6                  d|fd||f      t-        j0                  |      t-        j0                  |      dz  }dd|iz  }t3        t-        j4                  |            dx}x}}|
d   }d }||u }|slt-        j6                  d!|fd"||f      t-        j0                  |      t-        j0                  |      dz  }dd|iz  }t3        t-        j4                  |            dx}x}}|
d#   }d$}||k(  }|slt-        j6                  d|fd||f      t-        j0                  |      t-        j0                  |      dz  }dd|iz  }t3        t-        j4                  |            dx}x}}|
d%   }d$}||k(  }|slt-        j6                  d|fd||f      t-        j0                  |      t-        j0                  |      dz  }dd|iz  }t3        t-        j4                  |            dx}x}}|j                  |j                  dt        |             y)&u7   존재하지 않는 team_id 전달 시 기본값 반환r   Nc                       e Zd Z fdZy)DTestValidateModelConsistency.test_unknown_team_id.<locals>._FakeHomec                            S r  rQ   r  s     r2   r  zPTestValidateModelConsistency.test_unknown_team_id.<locals>._FakeHome.__truediv__k  r  rJ   Nr  r  s   r2   r  r  j  r  rJ   r  c                       e Zd Zd Zd Zy)HTestValidateModelConsistency.test_unknown_team_id.<locals>._FakeCokacdirc                     || _         y r  r  r  s     r2   r  zQTestValidateModelConsistency.test_unknown_team_id.<locals>._FakeCokacdir.__init__o  r  rJ   c                      | j                   |z  S r  r  r  s     r2   r  zTTestValidateModelConsistency.test_unknown_team_id.<locals>._FakeCokacdir.__truediv__r  r  rJ   Nr  rQ   rJ   r2   r  r  n  r  rJ   r  r  c                               S r  rQ   r  s   r2   rS   zCTestValidateModelConsistency.test_unknown_team_id.<locals>.<lambda>v  r  rJ   r   r   r<   r  ri   r}   r~   r  r  r  rV  r   r!  r"  r=  r#  r   r`   rb   rc   rf   rg   r  TrD  rF  r  r   r  )rv  r>   r  r  r   r  r   r  r   r    r!   r=   r?   r@   rC   r<   r  r  r!  r=  r   r   rj   r   rl   rm   rn   rk   )rp   r:   r  rv  r>   r  r  r  r*   rZ   r"  rr   rt   rq   rs   ru   r  r  s    `              @@r2   test_unknown_team_idz1TestValidateModelConsistency.test_unknown_team_idb  s   ))(377A	/ 	/	* 	*  ))GLL&,?R2ST(8:OPQ	y>)HHOOAs9~.$J''L*h?L*{F6KLL*v?Q6RSL)]C 99:LM&$''''''''z'''z''''''&'''&''''''$'''$''''''''''i 6$66 $66666 $6666 666$66666666l#+t+#t++++#t+++#+++t+++++++k"(b("b(((("b((("(((b(((((((k"(b("b(((("b((("(((b(((((((GLL&,}2MNrJ   N)r  )r  r  )r   r   r   r   r=   r  r  r  r  r  r  r  rQ   rJ   r2   r  r  n  s_    8S QW 6 M_#&FI	7Or1Of,O\-O^-OrJ   r  c                   D    e Zd ZdZddddeddfdZd Zd Zd	 Zd
 Z	d Z
y)TestSyncBotSettingsu%   _sync_bot_settings() 단위 테스트home_dirr   r  r   c                     |dz  }|j                  dd       |dz  }|dt        diddi}|j                  t        j                  |      d	
       |S )Nz	.cokacdirTrL   r  r  r  r  r  r   r   )rT   r  r   r%   r   )rp   r  r  r	  r  r   s         r2   _make_bot_settingsz&TestSyncBotSettings._make_bot_settings  si    k)td3 #66 0(*;<+
 	  D!1G DrJ   c                 &   ddl }ddl}t        t        j                  j                  dd            }t        |      |j                  vr%|j                  j                  dt        |             d|j                  v r|j                  d= ddl
}|dz  j                  dd       |j                  |d	|       |j                  j                  }|d
z  j                  dd       |j                  |j                  dt        fd             ||fS )uL   공통 monkeypatch: WORKSPACE와 Path.home()을 tmp_path 기반으로 교체r   Nr   r   r<   r   TrL   rE   	fake_homer  c                       S r  rQ   )r  s   r2   rS   z0TestSyncBotSettings._patch_env.<locals>.<lambda>  s    y rJ   )rv  r>   r   r   r    r!   r=   r?   r@   rC   r<   rT   r   r  r  )	rp   r:   r  rv  r>   r*   rZ   r  r  s	           @r2   
_patch_envzTestSyncBotSettings._patch_env  s    (8:OPQ	y>)HHOOAs9~.$J'' 
H	##D4#@L+x@  )){*	t4GLL&,?P2QRY55rJ   c                 T   | j                  ||      \  }}}ddl}| j                  |       |j                          |dz  dz  }|j                  } |       }	|	st        j                  d|       dz   dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |	      dz  }
t        t        j                  |
            dx}}	|j                  |j                  d	t        |             y)
u*   동기화 파일이 올바르게 생성됨r   Nr   bot_settings_sync.jsonu)   동기화 파일이 생성되지 않음: zC
>assert %(py4)s
{%(py4)s = %(py2)s
{%(py2)s = %(py0)s.exists
}()
}	sync_pathr   r  )r  rv  r  _sync_bot_settingsr"   rj   r   r   r   r   rl   rm   rn   r   r   r  )rp   r:   r  rZ   r  r  rv  r  r   rr   rt   s              r2   test_sync_creates_filez*TestSyncBotSettings.test_sync_creates_file  s    15;1W.i	*'')x'*BB	Z!Z!ZZ%Nyk#ZZZZZZZyZZZyZZZZZZ!ZZZZZZGLL&,}2MNrJ   c           	         | j                  ||      \  }}}ddl}| j                  |       |j                          |dz  dz  }t	        j
                  |j                  d            }|j                         D ],  \  }	}
|
j                  }d} ||      }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                  |      t        j                  |      dz  }t        j                  d|
j                  d             dz   d|iz  }t!        t        j"                  |            dx}x}x}x}}/ |j%                  |j&                  dt)        |             y)u(   token 값이 ***REDACTED***로 교체됨r   Nr   r  r   r   r  z***REDACTED***r`   zI%(py6)s
{%(py6)s = %(py2)s
{%(py2)s = %(py0)s.get
}(%(py4)s)
} == %(py9)scfgr   r   re   rg   r   u!   token이 마스킹되지 않음: z
>assert %(py11)sr   r  )r  rv  r  r  r%   r   r   rF  r!   rj   rk   r   r   r   rl   r   rm   rn   r   r   r  )rp   r:   r  rZ   r  r  rv  r  syncedr  r  r   rr   r   r   r   r   r   s                     r2   test_token_maskedz%TestSyncBotSettings.test_token_masked  sT   15;1W.i	*'')x'*BB	I///AB#\\^ 	pMHc77o7o77#o'7o#'77ooo#'7oooooo3ooo3ooo7ooo7ooo#ooo'7ooo;\]`]d]del]m\n9oooooooo	p 	GLL&,}2MNrJ   c                 L   | j                  ||      \  }}}ddl}| j                  |d       |j                          |dz  dz  }t	        j
                  |j                  d            }|j                  di       }	|	j                  }
d	} |
|      }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                  |      t        j                  |      dz  }dd|iz  }t        t        j                  |            dx}
x}x}x}}|	j                  }
d} |
|      }t         di}||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                  |      t        j                  |      dz  }dd|iz  }t        t        j                  |            dx}
x}x}x}}|j#                  |j$                  dt'        |             y)u'   token 외 필드는 그대로 유지됨r   Nr  )r  r   r  r   r   r  r  r`   r  r  r  r   r   r  r  r  )r  rv  r  r  r%   r   r   r!   rj   rk   r   r   r   rl   rm   rn   r  r   r   r  )rp   r:   r  rZ   r  r  rv  r  r  r  r   rr   r   r   r   r   r   s                    r2   test_other_fields_preservedz/TestSyncBotSettings.test_other_fields_preserved  s   15;1W.i	4FG'')x'*BB	I///ABjj+R0ww:~:w~&:*::&*:::::&*:::::::s:::s:::w:::~:::&:::*::::::::wwFxFwx F]4E$FF $FFFFF $FFFFFFFsFFFsFFFwFFFxFFF FFF$FFFFFFFFGLL&,}2MNrJ   c                 6   | j                  ||      \  }}}ddl}|j                          |dz  dz  }|j                  } |       }	|	 }
|
st	        j
                  d      dz   dt        j                         v st	        j                  |      rt	        j                  |      ndt	        j                  |      t	        j                  |	      dz  }t        t	        j                  |            dx}x}	}
|j                  |j                  d	t        |             y)
u<   원본 bot_settings.json 없을 때 graceful (에러 안남)r   Nr   r  u.   원본 없는데 동기화 파일이 생성됨zG
>assert not %(py4)s
{%(py4)s = %(py2)s
{%(py2)s = %(py0)s.exists
}()
}r  r   r  )r  rv  r  r"   rj   r   r   r   r   rl   rm   rn   r   r   r  )rp   r:   r  rZ   r  r  rv  r  r   rr   r   r   s               r2   r  z-TestSyncBotSettings.test_bot_settings_missing  s    15;1W.i 	'')x'*BB	##W#%W%%W%WW'WWWWWWW9WWW9WWW#WWW%WWWWWWGLL&,}2MNrJ   N)r  )r   r   r   r   r=   r  r  r  r   r  r  rQ   rJ   r2   r  r    s=    /6 S Z` 64OO"O$OrJ   r  c                   4    e Zd ZdZd Zd Zd Zd Zd Zd Z	y)	TestPrdDecompositionu*   PRD 자동 분해 (--prd) 기능 테스트c                 t
   d}ddl m}  ||      }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}
|	|
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}
|	|
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}
|	|
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g}
|	|
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}
|	|
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   d   }
|	|
v }|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}
|	|
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   기본 Phase 추출u   
## 3. 구현 로드맵

### Phase 1 (2일) — 기초 구현 [F1, F2]
- 항목 1
- 항목 2
- **DoD**: 기초 구현 완료

### Phase 2 (1일) — 통합 [F3]
- 통합 테스트
r   _parse_prd_regexry  r`   r  r   r  r   r  r   N
phase_typePhaserb   rc   rf   rg   phase_numberr6   titleu   기초 구현 [F1, F2]durationu   2일featuresF1F2dodr"  )z%(py1)s is not %(py4)su   기초 구현 완료r   r  r<   r  r   rj   rk   r   r   r   rl   rm   rn   rp   prd_contentr  r  rs   r   r   ru   r   rq   rr   rt   s               r2   test_parse_prd_regex_basicz/TestPrdDecomposition.test_parse_prd_regex_basic  s_   
 	.!+.6{a{a{ass66{aay&1'1&'1111&'111&111'1111111ay(-A-(A----(A---(---A-------ay!=%==!%=====!%====!===%========ay$..$....$...$..........ay$4t4$4444$444$4444444444ay+t+t++++t++++++t+++++++%95)99%)99999%)9999%999)99999999ay(-A-(A----(A---(---A-------rJ   c                 V   d}ddl m}  ||      }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}
|	|
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}
|	|
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   Sprint 0 포함 추출ua   
### Sprint 0 (0.5일) — 준비
- 준비 항목

### Phase 1 (2일) — 구현
- 구현 항목
r   r  ry  r`   r  r   r  r   r  r   Nr	  Sprintrb   rc   rf   rg   r  r6   r
  r  r  s               r2    test_parse_prd_regex_with_sprintz5TestPrdDecomposition.test_parse_prd_regex_with_sprint  s    	.!+.6{a{a{ass66{aay&2(2&(2222&(222&222(2222222ay(-A-(A----(A---(---A-------ay&1'1&'1111&'111&111'1111111rJ   c                 l   ddl m}  |d      }g }||k(  }|st        j                  d|fd||f      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}}y
)u   Phase 없는 문서r   r  u   # 빈 문서
내용만 있음r`   r   r  r   r   r   N)
r<   r  rj   rk   r   r   r   rl   rm   rn   )rp   r  r  rs   r   r   r   s          r2   test_parse_prd_regex_emptyz/TestPrdDecomposition.test_parse_prd_regex_empty0  sh    -!"BCv|vvvrJ   c                     ddl m}  |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   }||v }|slt        j                  d|fd||f      t        j                  |      t        j                  |      d	z  }d
d|iz  }t        t        j                  |            dx}x}}y)u%   존재하지 않는 PRD 파일 에러r   
handle_prdz/nonexistent/path.mdr^   r   r  r`   rb   rc   rf   rg   Nu   찾을 수 없습니다r  r   r  )r<   r  rj   rk   rl   rm   rn   )rp   r  r"  rq   rr   rs   rt   ru   s           r2    test_handle_prd_nonexistent_filez5TestPrdDecomposition.test_handle_prd_nonexistent_file7  s    '2K@h*7*7****7******7*******(=F9,==(,=====(,====(===,========rJ   c                    ddl }ddlm} ddlm} d}|dz  }|j                  |d       |d	z  d
z  }|j                  dd        |d|      5   |t        |      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}
|	|
k(  }|slt        j                  d|fd|	|
f      t        j                  |	      t        j                  |
      dz  }dd|iz  }t        t        j                  |            dx}	x}}
|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    }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!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}}
|j%                  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# 1 sw Y   xY w)-u   task 파일 생성 확인r   Nr   r  u   
## 3. 구현 로드맵

### Phase 1 (2일) — 기초 구현 [F1, F2]
- 항목 1
- **DoD**: 구현 완료

### Phase 2 (1일) — 통합
- 통합 항목
zprd-test-project.mdr   r   r   rO   TrL   dispatch.WORKSPACEr^   r   r  r`   rb   rc   rf   rg   methodregextotal_phasesry  createdz0%(py4)s
{%(py4)s = %(py0)s(%(py2)s)
} == %(py7)sr   rZ  r>  r   skippedzdispatch-test-project-phase1.mdr   phase1_filer   ztask_id:r   r  rh  r	  r   r   zF1, F2)r   unittest.mockr   r<   r  r   rT   r=   rj   rk   rl   rm   rn   r   r   r   r   r"   r   )rp   r:   r   r   r  r  prd_file	tasks_dirr"  rq   rr   rs   rt   ru   r   r   r   r?  r   r(  rh  r   r   s                          r2   test_handle_prd_creates_filesz2TestPrdDecomposition.test_handle_prd_creates_files?  s   ''	 33K': x''1	t4'2 	<H{;F	< h'4'4''''4''''''4'''''''h*7*7****7******7*******n%**%****%***%**********)$*s$%**%****%******s***s***$***%**********)$*s$%**%****%******s***s***$***%**********  "CC!!#!########{###{###!##########'''9$zW$$$$zW$$$z$$$$$$W$$$W$$$$$$$%{g%%%%{g%%%{%%%%%%g%%%g%%%%%%%"x7""""x7"""x""""""7"""7"""""""	< 	<s   Y++Y5c                    ddl m} ddlm} d}|dz  }|j	                  |d       |dz  d	z  }|j                  d
d
       |dz  }|j	                  dd        |d|      5   |t        |      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   }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   }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}}|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   DxY w)!u   기존 파일 스킵 확인r   r   r  u+   
### Phase 1 (1일) — 테스트
- 항목
zprd-skip-test.mdr   r   r   rO   TrL   zdispatch-skip-test-phase1.mdr   r!  r^   Nr   r  r`   rb   rc   rf   rg   r%  r&  r   rZ  r>  r   r'  r6   )zH%(py4)s
{%(py4)s = %(py2)s
{%(py2)s = %(py0)s.read_text
}()
} == %(py7)s)r)  r   r<   r  r   rT   r=   rj   rk   rl   rm   rn   r   r   r   r   r   )rp   r:   r   r  r  r*  r+  r   r"  rq   rr   rs   rt   ru   r   r   r   r?  r   s                      r2   test_handle_prd_skips_existingz3TestPrdDecomposition.test_handle_prd_skips_existingi  s   '' 00K':x''1	t4==J9'2 	<H{;F	< h'4'4''''4''''''4''''''')$*s$%**%****%******s***s***$***%**********)$*s$%**%****%******s***s***$***%**********!!1!#1z1#z1111#z111111x111x111!111#111z1111111	< 	<s   #N88ON)
r   r   r   r   r  r  r  r  r,  r.  rQ   rJ   r2   r  r    s$    4.42">(#T2rJ   r  c                       e Zd ZdZd Zy)TestDispatchDelayFixeduP   task-2139: wake-up 제거 후 dispatch delay가 항상 10초 고정인지 검증c                    ddl }d}t        ||      }| }|st        j                  d      dz   dt	        j
                         v st        j                  t              rt        j                  t              nddt	        j
                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      dz  }t        t        j                  |            dx}x}}d	}t        ||      }|st        j                  d
      dz   dt	        j
                         v st        j                  t              rt        j                  t              nddt	        j
                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      dz  }t        t        j                  |            dx}}y)u>   _wake_up_bot 제거 후 _dispatch_delay는 항상 10초 고정r   N_wake_up_botz_wake_up_bot should be removedz;
>assert not %(py5)s
{%(py5)s = %(py0)s(%(py1)s, %(py3)s)
}hasattrrY   )r   rd   r   r   rU   z%_check_bot_process should still existz7
>assert %(py5)s
{%(py5)s = %(py0)s(%(py1)s, %(py3)s)
})
r<   r3  rj   r   r   r   r   rl   rm   rn   )rp   rY   rs   r   r   ru   r   s          r2   test_dispatch_delay_is_fixed_10z6TestDispatchDelayFixed.test_dispatch_delay_is_fixed_10  s    !/Q73/Q//Q/QQ1QQQQQQQ7QQQ7QQQQQQ3QQQ3QQQQQQ/QQQQQQ 1Zws01Z1ZZ3ZZZZZZZwZZZwZZZZZZsZZZsZZZ0ZZZ1ZZZZZZrJ   N)r   r   r   r   r4  rQ   rJ   r2   r0  r0    s    Z[rJ   r0  )Er   r  r   _pytest.assertion.rewrite	assertionrewriterj   r%   r   r>   typesr   rv  r   r)  r   r   r  r    r!   r  r#   r3   r  r9   
ModuleTyperI   r  rZ   r\   r   r   r  r:  rT  ri  r  r  r  rg  r  r  r  r  r  r  r9  rQ  r  r  r  r  r  r  r  r  r  r  r  r  r"  r<  rI  ra  rq  r  r  r  r0  rQ   rJ   r2   <module>r:     sw  
   	 
    * 

1<@S &s :D U5E5E > 6 62G G./# /#nl% l%h D" ":( (2J+ J+pM- M-jT" T"x^k ^kLY! Y!B'= '=^BV BVTd~ d~XXs Xs@@2 @2PWB WBtXx Xx@* *N`,[,W65# 5#zY# Y#B9 9F9 9<&) &)\C% C%VlU lUh89 89@H0 H0`7E 7E~`? `?Pu( u(zn0 n0lJ@ J@daO aOR	hO hOVA2 A2H[ [rJ   