
    i             	          d Z ddlZddlmc mZ ddlZddlZddl	Z	ddl
Z
ddlZddlmZ ddlmZmZmZ ddlZd Z e         eej(                  j+                  dd            Zedz  d	z  Z ee      e	j2                  vr"e	j2                  j5                  d ee             d
efdZ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% ejL                         d        Z' ejL                         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.      Z0 G d/ d0      Z1 G d1 d2      Z2 G d3 d4      Z3 G d5 d6      Z4 G d7 d8      Z5i d9d:d;d<d=d=d=d>d?d@d;dAd=d=d=d>dBdCd;dDd=d=d=d>dEdFd;dGd=d=d=d>dHdId;dJd=d=d=d>dKdLdMdNd=d=d=d>dOdPdMdAd=d=d=d>dQdRdMdDd=d=d=d>dSdTdMdUd=d=d=d>dVdWdMdJd=d=d=d>dXdYdZd[d=d=d=d>d\d]dZdAd=d=d=d>d^d_dZdDd=d=d=d>d`dadZdUd=d=d=d>dbdcdZdJd=d=d=d>dddedfdgd=d=d=d>dhdidjdkd=d=d=d>dldmdnd=d=d=d>dodpdqd=d=d=d>drZ6 G ds dt      Z7 G du dv      Z8y)wu  
test_group_chat.py

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

테스트 항목:
1. load_env_keys: 환경변수 로드 정상 작동
2. load_bot_token: 환경변수 / .env.keys 폴백 / RuntimeError
3. load_personas: 조직도 우선, personas.json 폴백, DEFAULT_PERSONAS 폴백
4. load_personas_from_org: 조직도 파싱 상세 검증
5. format_persona_tag: 태그 포맷 검증
6. EMOJI_MAP: 이모지 매핑 검증
7. read_trigger: 트리거 파일 읽기 + 삭제 확인
8. dump_session / load_session: 세션 상태 파일 덤프/복구
9. GroupChatSession.start: 입장 시퀀스 정상 동작 (send 호출 횟수)
10. GroupChatSession.end: 퇴장 시퀀스 + 세션 파일 정리
11. GroupChatSession.add_user_input: auto_turns 리셋, 히스토리 추가
12. select_next_speaker: 직전 발화자 제외, 폴백 로직
13. MAX_AUTO_TURNS 제한: auto_turns >= 6 시 대기
    N)Path)	MagicMock	mock_openpatchc                     dt         j                  vr_t        j                  d      } t	        t	        d            | _        t	        t	        d            | _        | t         j                  d<   yt         j                  d   }t        |d      st	        t	        d            |_        t        |d      st	        t	        d            |_        yy)u/   requests 스텁을 sys.modules에 등록한다.requestsT)okreturn_valuegetpostN)sysmodulestypes
ModuleTyper   r   r   hasattr)requests_stubstubs     ,/home/jay/workspace/tests/test_group_chat.py_inject_stubsr   '   s     $((4&I4FG%93EF"/J {{:&tU# i4.@ADHtV$!yD/ABDI %    WORKSPACE_ROOTz/home/jay/workspacememoryzorganization-structure.jsonreturnc                     t         j                         syt        t         d      5 } t        j                  |       }ddd       t               }j                  di       }|j                  di       j                  dg       D ]  }|j                  d      d	k7  r|j                  d
      dk(  r|j                  dg       D ]  }|j                  d      d	k7  r|j                  d      xs i }|j                  d      r0|d   dk7  r(|j                  d      d	k(  r|j                  |d          |j                  dg       D ]?  }|j                  d      dk(  r|j                  d      dv s,|j                  |d          A  |j                  d      xs i }|j                  d      rC|d   dk7  r;|j                  d      r*|j                  dd	      }|dv r|j                  |d          |j                  dg       D ]B  }|j                  d      dk(  r|j                  dd	      }	|	dv s/|j                  |d          D  |j                  di       j                  dg       D ]  }
|
j                  d      d	k7  r|
j                  d      xs i }|j                  d      r/|d   dk7  r'|j                  d      dv r|j                  |d          |
j                  dg       D ]B  }|j                  d      dk(  r|j                  dd	      }	|	dv s/|j                  |d          D  t        |      S # 1 sw Y   xY w)uu   organization-structure.json에서 load_personas_from_org와 동일한 로직으로 활성 인원 수를 계산한다.utf-8encodingN	structurecolumnsteamsstatusactiveteam_idzdevelopment-office	sub_teamsleadidanumembers)r$   	availablenamerowscenters)	ORG_STRUCTURE_PATHexistsopenjsonloadsetr   addlen)forgseenr    teamsub_teamr'   memberlead_statusmember_statuscenters              r   _count_expected_org_personasr@   E   s   $$&	 7	3 qiilDR(I i,00"= +88H)88I"66 HH["5 
/<<)X5||F+1r88D>d4jE&9dhhx>PT\>\HHT$Z(&ll9b9 /Fzz$'50 zz(+/FF.	/
/ 88F#)rDxx~$t*"5$((6:J"hhx:"99HHT$Z(((9b1 +::d#u, &

8X > $;;HHVD\*+-+< --+//	2> '::h8+zz&!'R88D>d4jE1dhhx6HLc6cHHT$Z jjB/ 	'Fzz$5("JJx:M 77&	'' t9c s   L>>Mc                      t                t        t        j                  j	                               D ]  } | dk(  s	t        j                  | =  ddl}|S )u_   sys.modules에서 group_chat을 제거해 매 호출마다 새로운 상태로 임포트한다.
group_chatr   N)r   listr   r   keysrB   )keygcs     r   _fresh_modulerG   }   sF    OCKK$$&' !,C ! Ir   c                   (    e Zd ZdZd Zd Zd Zd Zy)TestLoadEnvKeysu   load_env_keys 함수 테스트.c                    t               }t        j                  t        j                  ddid      5  t        d      5 }|j                          |j                          ddd       ddd       y# 1 sw Y   xY w# 1 sw Y   yxY w)uN   GROUP_CHAT_BOT_TOKEN이 이미 있으면 subprocess를 호출하지 않는다.GROUP_CHAT_BOT_TOKENzalready-setFclearsubprocess.runN)rG   r   dictosenvironload_env_keysassert_not_called)selfrF   mock_runs      r   !test_skips_when_token_already_setz1TestLoadEnvKeys.test_skips_when_token_already_set   sv    _ZZ

%;]$KSXY 	-'( -H  "**,-	- 	-- -	- 	-s"   A=!A1 A=1A:	6A==Bc                 |   t               }|dz  }|j                  d       t               }d|_        t        j
                  j                         D ci c]  \  }}|dk7  s|| }}}t        j                  t        j
                  |d      5  t        j                  |dt        |            5  t        d|	      5  |j                          t        j
                  }|j                  }	d}
 |	|
      }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$                  |
      t        j$                  |      t        j$                  |      dz  }dd|iz  }t'        t        j(                  |            dx}x}	x}
x}x}}ddd       ddd       ddd       yc c}}w # 1 sw Y    xY w# 1 sw Y   $xY w# 1 sw Y   yxY w)uW   GROUP_CHAT_BOT_TOKEN이 없을 때 .env.keys에서 읽어 환경변수에 설정한다.	.env.keysz1export GROUP_CHAT_BOT_TOKEN=test-token-from-file
z8GROUP_CHAT_BOT_TOKEN=test-token-from-file
OTHER_VAR=foo
rK   TrL   ENV_KEYS_FILErN   r
   ztest-token-from-file==)zg%(py8)s
{%(py8)s = %(py4)s
{%(py4)s = %(py2)s
{%(py2)s = %(py0)s.environ
}.get
}(%(py6)s)
} == %(py11)srP   py0py2py4py6py8py11assert %(py13)spy13N)rG   
write_textr   stdoutrP   rQ   itemsr   rO   objectstrrR   r   
@pytest_ar_call_reprcompare@py_builtinslocals_should_repr_global_name	_safereprAssertionError_format_explanation)rT   tmp_pathrF   env_filefake_resultkvenv_without_token@py_assert1@py_assert3@py_assert5@py_assert7@py_assert10@py_assert9@py_format12@py_format14s                   r   test_loads_token_from_env_filez.TestLoadEnvKeys.test_loads_token_from_env_file   s   _k)PQkY.0jj.>.>.@`daAI_D_QT``ZZ

$5TB 	\b/3x=A \++F \$$&::[:>>[*@[>*@A[E[[AE[[[[[AE[[[[[[[2[[[2[[[:[[[>[[[*@[[[A[[[E[[[[[[[[[\\	\ 	\ a\ \\ \	\ 	\sI   H!H!H20H&>D=H;H&H2H#H&&H/	+H22H;c           
         t               }|dz  }|j                  d       t               }d|_        t        j
                  j                         D ci c]  \  }}|dk7  s|| }}}t        j                  t        j
                  |d      5  t        j                  |dt        |            5  t        d|	      5  |j                          d}t        j
                  }	||	v}
|
st        j                  d
|
fd||	f      t        j                  |      dt        j                          v st        j"                  t              rt        j                  t              ndt        j                  |	      dz  }dd|iz  }t%        t        j&                  |            dx}x}
}	ddd       ddd       ddd       yc c}}w # 1 sw Y    xY w# 1 sw Y   $xY w# 1 sw Y   yxY w)u^   subprocess 출력에 GROUP_CHAT_BOT_TOKEN이 없으면 환경변수가 설정되지 않는다.rX   zexport SOME_OTHER_VAR=value
zSOME_OTHER_VAR=value
rK   TrL   rY   rN   r
   not in)z3%(py1)s not in %(py5)s
{%(py5)s = %(py3)s.environ
}rP   py1py3py5assert %(py7)spy7N)rG   re   r   rf   rP   rQ   rg   r   rO   rh   ri   rR   rj   rk   ro   rl   rm   rn   rp   rq   )rT   rr   rF   rs   rt   ru   rv   rw   @py_assert0@py_assert4@py_assert2@py_format6@py_format8s                r   test_no_token_in_env_filez)TestLoadEnvKeys.test_no_token_in_env_file   si   _k);<k5.0jj.>.>.@`daAI_D_QT``ZZ

$5TB 	Db/3x=A D++F D$$&1CC1CCCC1CCC1CCCCCCCCCCCCCCCCCCCDD	D 	D aD DD D	D 	DsI   F;!F;!G0G>C$G"G*GG
GG	GG"c                    t               }|dz  }t        j                  j                         D ci c]  \  }}|dk7  s|| }}}t	        j
                  t        j                  |d      5  t	        j                  |dt        |            5  |j                          ddd       ddd       yc c}}w # 1 sw Y   xY w# 1 sw Y   yxY w)u8   env.keys 파일이 없어도 예외 없이 반환한다.znonexistent.env.keysrK   TrL   rY   N)	rG   rP   rQ   rg   r   rO   rh   ri   rR   )rT   rr   rF   missing_pathru   rv   rw   s          r   $test_missing_env_file_does_not_raisez4TestLoadEnvKeys.test_missing_env_file_does_not_raise   s    _"88.0jj.>.>.@`daAI_D_QT``ZZ

$5TB 	#b/3|3DE #  "#	# 	# a# #	# 	#s.   B0B0-!CB6C6B?	;CCN)__name__
__module____qualname____doc__rV   r   r   r    r   r   rI   rI      s    )-\ D #r   rI   c                   (    e Zd ZdZd Zd Zd Zd Zy)TestLoadBotTokenuK   load_bot_token 함수 테스트 - GROUP_CHAT_BOT_TOKEN 환경변수 기반.c                    t               }t        j                  t        j                  ddid      5  |j                         }d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  }d
d|iz  }t        t        j                  |            dx}}y# 1 sw Y   xY w)u?   GROUP_CHAT_BOT_TOKEN 환경변수에서 토큰을 로드한다.rK   z1234567:ENV_TOKENFrL   NrZ   z%(py0)s == %(py3)stokenr]   r   assert %(py5)sr   )rG   r   rO   rP   rQ   load_bot_tokenrj   rk   rl   rm   rn   ro   rp   rq   )rT   rF   r   r   rx   @py_format4r   s          r   test_loads_from_env_varz(TestLoadBotToken.test_loads_from_env_var   s    _ZZ

%;=P$QY^_ 	(%%'E	(++u+++++u+++++++u+++u+++++++++++	( 	(s   C33C<c                 p   t               }|dz  }|j                  d       t               }d|_        t        j
                  j                         D ci c]  \  }}|dk7  s|| }}}t        j                  t        j
                  |d      5  t        j                  |dt        |            5  t        d|	      5  |j                         }d
d
d
       d
d
d
       d
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  }dd|iz  }t%        t        j&                  |            d
x}
}	y
c c}}w # 1 sw Y   xY w# 1 sw Y   xY w# 1 sw Y   xY w)u<   환경변수 없을 때 .env.keys에서 폴백 로드한다.rX   z0export GROUP_CHAT_BOT_TOKEN=9999:FALLBACK_TOKEN
z1GROUP_CHAT_BOT_TOKEN=9999:FALLBACK_TOKEN
OTHER=x
rK   TrL   rY   rN   r
   Nz9999:FALLBACK_TOKENrZ   r   r   r   r   r   )rG   re   r   rf   rP   rQ   rg   r   rO   rh   ri   r   rj   rk   rl   rm   rn   ro   rp   rq   )rT   rr   rF   rs   rt   ru   rv   rw   r   r   rx   r   r   s                r   !test_loads_from_env_keys_fallbackz2TestLoadBotToken.test_loads_from_env_keys_fallback   sG   _k)OPkR.0jj.>.>.@`daAI_D_QT``ZZ

$5TB 	0b/3x=A 0++F 0--/E00	0
 .-u-----u-------u---u----------- a0 00 0	0 	0sH   F!F!F,0F >FF F,FF  F)	%F,,F5c           	         t               }|dz  }|j                  d       t               }d|_        t        j
                  j                         D ci c]  \  }}|dk7  s|| }}}t        j                  t        j
                  |d      5  t        j                  |dt        |            5  t        d|	      5  t        j                  t              5  |j                          d
d
d
       d
d
d
       d
d
d
       d
d
d
       y
c c}}w # 1 sw Y   (xY w# 1 sw Y   ,xY w# 1 sw Y   0xY w# 1 sw Y   y
xY w)uX   환경변수도 없고 .env.keys에서도 못 찾으면 RuntimeError를 발생시킨다.rX   zexport OTHER_VAR=something
zOTHER_VAR=something
rK   TrL   rY   rN   r
   N)rG   re   r   rf   rP   rQ   rg   r   rO   rh   ri   pytestraisesRuntimeErrorr   )rT   rr   rF   rs   rt   ru   rv   rw   s           r   test_raises_when_no_token_foundz0TestLoadBotToken.test_raises_when_no_token_found   s   _k):;k4.0jj.>.>.@`daAI_D_QT``ZZ

$5TB 	,b/3x=A ,++F ,|4 ,))+,,,	, 	, a, ,, ,, ,	, 	,s`   D
!D
!D40D(>DD	)D1D(9D4DDD%!D((D1	-D44D=c                    t               }|dz  }|j                  d       t               }d|_        t	        j
                  t        j                  ddid      5  t	        j                  |dt        |            5  t	        d	|
      5 }|j                         }|j                          ddd       ddd       d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  }	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)uI   환경변수와 .env.keys 모두 있을 때 환경변수가 우선한다.rX   z+export GROUP_CHAT_BOT_TOKEN=FALLBACK_TOKEN
z$GROUP_CHAT_BOT_TOKEN=FALLBACK_TOKEN
rK   ENV_PRIORITY_TOKENFrL   rY   rN   r
   NrZ   r   r   r   r   r   )rG   re   r   rf   r   rO   rP   rQ   rh   ri   r   rS   rj   rk   rl   rm   rn   ro   rp   rq   )rT   rr   rF   rs   rt   rU   r   r   rx   r   r   s              r   test_env_var_takes_priorityz,TestLoadBotToken.test_env_var_takes_priority   s%   _k)JKkDZZ

%;=Q$RZ_` 	1b/3x=A 1++F 1(--/E..011	1 -,u,,,,,u,,,,,,,u,,,u,,,,,,,,,,,1 11 1	1 	1s<   !F;E5	!E)*E52F)E2.E55E>	:FF
N)r   r   r   r   r   r   r   r   r   r   r   r   r      s    U,.", -r   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
)TestLoadPersonasuH   load_personas 함수 테스트 - 조직도 우선, personas.json 폴백.c                    t               }t        j                         st        j                  d       |j                         }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}t        |      }d}||kD  }|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)u\   조직도 파일에서 페르소나를 로드한다 (실제 파일 기반 통합 테스트).3   organization-structure.json 파일이 없습니다.5assert %(py4)s
{%(py4)s = %(py0)s(%(py1)s, %(py2)s)
}
isinstanceresultrO   r]   r   r^   r_   Nr   >z/%(py3)s
{%(py3)s = %(py0)s(%(py1)s)
} > %(py6)sr6   r]   r   r   r`   assert %(py8)sra   )rG   r/   r0   r   skipload_personasr   rO   rl   rm   rj   rn   ro   rp   rq   r6   rk   )
rT   rF   r   ry   @py_format5r   rz   r   @py_format7@py_format9s
             r   test_loads_from_org_structurez.TestLoadPersonas.test_loads_from_org_structure  sC   _!((*KKMN!!#&$''''''''z'''z''''''&'''&''''''$'''$''''''''''6{Q{Q{Qss66{Qr   c                    t               }t        j                         s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	)
uI   anu가 조직도 로드 결과에서 제외된다 (실제 파일 기반).r   r)   r   z%(py1)s not in %(py3)sr   r   r   r   r   N)rG   r/   r0   r   r   r   rj   rk   ro   rl   rm   rn   rp   rq   rT   rF   r   r   r   r   r   s          r   test_excludes_anu_from_orgz+TestLoadPersonas.test_excludes_anu_from_org  s    _!((*KKMN!!#"uF""""uF"""u""""""F"""F"""""""r   c           
      p   t               }dddddddddd	g d
gidg idi}t        dt        t        j                  |                  5  t        dd      5  |j                         }dd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# 1 sw Y   xY w# 1 sw Y   xY w)uG   planned 상태인 항목은 조직도 로드 결과에서 제외된다.r    r"   zstrategy-teamu	   전략팀plannedzeusu	   제우스u   팀장)r(   r,   r#   role)r%   	team_namer#   r'   r*   r.   )r!   r-   zbuiltins.open)	read_datazpathlib.Path.existsTr
   Nr   r   r   r   r   r   )rG   r   r   r2   dumpsload_personas_from_orgrj   rk   ro   rl   rm   rn   rp   rq   )rT   rF   fake_orgr   r   r   r   r   s           r   test_excludes_planned_statusz-TestLoadPersonas.test_excludes_planned_status   s   _ '6)4&/&,(3*3(0	% (*  #B#
* ?I

88L$MN 	5,4@ 52245	5 #vV####vV###v######V###V#######5 5	5 	5s$   D,D *D, D)	%D,,D5c                    t               }ddddddddi}|d	z  }|j                  t        j                  |d
      d       t	        j
                  |dd      5  t	        j
                  |dt        |            5  |j                         }dd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   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=   조직도 로드 실패 시 personas.json으로 폴백한다.
custom_botu   커스텀봇   테스트팀u	   테스터QAu	   꼼꼼함 r,   r:   r   	expertisepersonalitypersona_desczpersonas.jsonFensure_asciir   r   ORG_STRUCTURE_FILE/nonexistent/path/org.jsonPERSONAS_FILENinz%(py1)s in %(py3)sr   r   r   r   r,   rZ   z%(py1)s == %(py4)sr   r_   assert %(py6)sr`   )rG   re   r2   r   r   rh   ri   r   rj   rk   ro   rl   rm   rn   rp   rq   )rT   rr   rF   custompersonas_filer   r   r   r   r   ry   r   r   s                r    test_falls_back_to_personas_jsonz1TestLoadPersonas.test_falls_back_to_personas_json>  sf   _&&#!* "	
 !?2  F!GRY Z\\"24PQ 	,b/3}3EF ,))+,	, %|v%%%%|v%%%|%%%%%%v%%%v%%%%%%%l#F+=~=+~====+~===+===~=======	, ,	, 	,s$   !G;GGG	
GGc                    t               }|dz  }t        j                  |dd      5  t        j                  |dt        |            5  |j	                         }ddd       ddd       |j
                  }|u }|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	t        j                  |      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:   모든 소스 실패 시 DEFAULT_PERSONAS를 반환한다.zno_personas.jsonr   r   r   Nis)z8%(py0)s is %(py4)s
{%(py4)s = %(py2)s.DEFAULT_PERSONAS
}r   rF   r]   r^   r_   r   r`   )rG   r   rh   ri   r   DEFAULT_PERSONASrj   rk   rl   rm   rn   ro   rp   rq   )	rT   rr   rF   missing_personasr   ry   rx   r   r   s	            r   test_falls_back_to_defaultsz,TestLoadPersonas.test_falls_back_to_defaultsU  s    _#&88\\"24PQ 	,b/37G3HI ,))+,	, ,,,v,,,,,v,,,,,,,v,,,v,,,,,,,,,,,,,,,,,,,,, ,	, 	,s#   !E'EE'E$	E''E1c                    t               }t        j                         st        j                  d       |j                         }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}}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                  |      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^   조직도에서 기대 인원 수를 로드한다 (anu 제외, planned 제외, 동적 계산).r   r   r   z%(py0)s > %(py3)sexpected_countr   :   organization-structure.json에서 인원 수 계산 실패
>assert %(py5)sr   NrZ   z0%(py3)s
{%(py3)s = %(py0)s(%(py1)s)
} == %(py5)sr6   r   r]   r   r   r   r   r   rG   r/   r0   r   r   r   r@   rj   rk   rl   rm   rn   ro   _format_assertmsgrp   rq   r6   
rT   rF   r   r   r   rx   r   r   r   r   s
             r   test_org_loads_expected_countz.TestLoadPersonas.test_org_loads_expected_count`  s5   _!((*KKMN**,57 !_~!___~______~___~______#_______6{,{n,,,,{n,,,,,,s,,,s,,,,,,6,,,6,,,{,,,,,,n,,,n,,,,,,,r   c                    t               }h d}|j                  }|j                  }|j                  } |       }t	        |      } ||      }|sgddt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      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                  |      t        j                  |      d	z  }	t        t        j                  |	            dx}x}x}x}x}}y)u?   DEFAULT_PERSONAS에 필수 페르소나가 포함되어 있다.>   irislokiodinthorathenahermesvulcanzassert %(py14)s
{%(py14)s = %(py2)s
{%(py2)s = %(py0)s.issubset
}(%(py12)s
{%(py12)s = %(py3)s(%(py10)s
{%(py10)s = %(py8)s
{%(py8)s = %(py6)s
{%(py6)s = %(py4)s.DEFAULT_PERSONAS
}.keys
}()
})
})
}expected_keysr4   rF   	r]   r^   r   r_   r`   ra   py10py12py14N)rG   issubsetr   rD   r4   rl   rm   rj   rn   ro   rp   rq   
rT   rF   r   rx   rz   r{   r}   @py_assert11@py_assert13@py_format15s
             r   +test_default_personas_contain_expected_keysz<TestLoadPersonas.test_default_personas_contain_expected_keysj  s   _V%%F"*=*=F*=*B*BF*B*DFc*D&EF%&EFFFFFFFF}FFF}FFF%FFFFFFcFFFcFFFFFF"FFF"FFF*=FFF*BFFF*DFFF&EFFFFFFFFFFFr   c                    t               }|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  }t        j                  | d      dz   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  }t        j                  | d      dz   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  }t        j                  | d      dz   d|iz  }t        t        j                  |            d	x}}C y	)u]   DEFAULT_PERSONAS 각 항목이 team, persona_desc 필드를 가지고 emoji 필드가 없다.r:   r   r   personar   u   에 team 필드 없음r   r   Nr   u   에 persona_desc 필드 없음emojir   r   u%   에 emoji 필드가 있으면 안 됨)rG   r   rg   rj   rk   ro   rl   rm   rn   r   rp   rq   )rT   rF   rE   r  r   r   r   r   s           r   %test_default_personas_have_new_fieldsz6TestLoadPersonas.test_default_personas_have_new_fieldsp  st   _//557 	YLCD6W$DDD6WDDD6DDDDDDWDDDWDDDD-C&DDDDDDD!T>W,TTT>WTTT>TTTTTTWTTTWTTTT5S.TTTTTTTX7')XXX7'XXX7XXXXXX'XXX'XXXXcU2W+XXXXXXXX	Yr   N)r   r   r   r   r   r   r   r   r   r   r	  r  r   r   r   r   r     s/    R#$<>.	--GYr   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
)TestLoadPersonasFromOrgu/   load_personas_from_org 함수 상세 테스트.c                 
   t               }t        j                         s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}||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}||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}||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}||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}||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}||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}||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	)uQ   개발1팀, 개발2팀, 개발3팀 멤버를 파싱한다 (실제 파일 기반).r   r   r   r   r   r   r   r   Nr:   
   개발1팀rZ   r   r   r   r`   r   r   r   r   
   개발2팀r   rau
   개발8팀anubisrG   r/   r0   r   r   r   rj   rk   ro   rl   rm   rn   rp   rq   
rT   rF   r   r   r   r   r   ry   r   r   s
             r   test_parses_dev_teamsz-TestLoadPersonasFromOrg.test_parses_dev_teams  s   _!((*KKMN**, !x6!!!!x6!!!x!!!!!!6!!!6!!!!!!!h'7<7'<7777'<777'777<7777777!x6!!!!x6!!!x!!!!!!6!!!6!!!!!!!h'7<7'<7777'<777'777<7777777vvvf~f%55%5555%555%5555555555!x6!!!!x6!!!x!!!!!!6!!!6!!!!!!!h'7<7'<7777'<777'777<7777777 vvvf~f%55%5555%555%5555555555vvvf~f%55%5555%555%5555555555 tv~tvtvvd|F#3|3#|3333#|333#333|3333333!x6!!!!x6!!!x!!!!!!6!!!6!!!!!!!h'7<7'<7777'<777'777<7777777r   c                    t               }t        j                         s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@   보안팀 리더(로키)를 파싱한다 (실제 파일 기반).r   r   r   r   r   r   r   r   Nr:   	   보안팀rZ   r   r   r   r`   r   u   보안팀장 (Primary)r  r  s
             r   test_parses_security_teamz1TestLoadPersonasFromOrg.test_parses_security_team  s9   _!((*KKMN**,vvvf~f%44%4444%444%4444444444f~f%A)AA%)AAAAA%)AAAA%AAA)AAAAAAAAr   c                 z   t               }t        j                         s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}||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}||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	)u`   횡단조직(QC 센터, DevOps 센터, 디자인 센터)을 파싱한다 (실제 파일 기반).r   maatr   r   r   r   r   r   Nr:   	   QC 센터rZ   r   r   r   r`   janus   DevOps 센터venusu   Gemini 센터r  r  s
             r   test_parses_centersz+TestLoadPersonasFromOrg.test_parses_centers  s=   _!((*KKMN**,vvvf~f%44%4444%444%4444444444 w&    w&   w      &   &       gv&9/9&/9999&/999&999/9999999 w&    w&   w      &   &       gv&9/9&/9999&/999&999/9999999r   c                    t               }t        j                         st        j                  d       |j                         }h d}|j                         D ]  \  }}|t        |j                               z
  }| }|st        j                  | d|       dz   ddt        j                         v st        j                  |      rt        j                  |      ndiz  }t        t        j                   |            d} y)u\   각 페르소나에 name, team, role, expertise, personality, persona_desc 필드가 있다.r   >   r,   r   r:   r   r   r   u   에 누락된 필드: z
>assert not %(py0)sr]   missingN)rG   r/   r0   r   r   r   rg   r4   rD   rj   r   rl   rm   rn   ro   rp   rq   )	rT   rF   r   required_fieldsrE   r  r#  rx   @py_format2s	            r    test_persona_has_required_fieldsz8TestLoadPersonasFromOrg.test_persona_has_required_fields  s    _!((*KKMN**,^"LLN 	HLC%GLLN(;;G;G;GG3%'=gY GGGGGGGwGGGwGGGGGG	Hr   c                    t               }t        j                         st        j                  d       |j                         }|d   d   }d}||k(  }|slt        j                  d|fd||f      t        j                  |      t        j                  |      dz  }dd	|iz  }t        t        j                  |            d
x}x}}|d   d   }d}||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
)uK   '헤르메스 (Hermes)' 형태의 이름을 '헤르메스'로 파싱한다.r   r   r,      헤르메스rZ   r   r   r   r`   Nr      로키r  	   마아트)rG   r/   r0   r   r   r   rj   rk   ro   rp   rq   rT   rF   r   r   ry   r   r   r   s           r   &test_name_parsing_strips_parentheticalz>TestLoadPersonasFromOrg.test_name_parsing_strips_parenthetical  s5   _!((*KKMN**, h'9>9'>9999'>999'999>9999999f~f%11%1111%111%1111111111f~f%44%4444%444%4444444444r   c                    t               }t        j                         s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	)
u!   anu는 결과에서 제외된다.r   r)   r   r   r   r   r   r   Nr  r   s          r   test_excludes_anuz)TestLoadPersonasFromOrg.test_excludes_anu  s    _!((*KKMN**,"uF""""uF"""u""""""F"""F"""""""r   c                    t               }t        j                         st        j                  d       |j                         }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}}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                  |      dt        j                         v st        j                  |      rt        j                  |      nddz  }dd|iz  }	t        t        j                  |	            d
x}}y
)uK   planned 상태인 팀은 파싱하지 않는다 (인원 수 동적 검증).r   r   r   r   r   r   r   r   r   NrZ   r   r6   r   r   r   r   r   r   s
             r   test_excludes_planned_teamsz3TestLoadPersonasFromOrg.test_excludes_planned_teams  s7   _!((*KKMN**, 67 !_~!___~______~___~______#_______6{,{n,,,,{n,,,,,,s,,,s,,,,,,6,,,6,,,{,,,,,,n,,,n,,,,,,,r   c                    t               }t        j                  |dt        |dz              5  t	        j
                  t              5  |j                          ddd       ddd       y# 1 sw Y   xY w# 1 sw Y   yxY w)u8   조직도 파일이 없으면 예외를 발생시킨다.r   znonexistent.jsonN)rG   r   rh   ri   r   r   	Exceptionr   )rT   rr   rF   s      r   test_raises_on_missing_org_filez7TestLoadPersonasFromOrg.test_raises_on_missing_org_file  sl    _\\"2CCU8U4VW 	,y) ,))+,	, 	,, ,	, 	,s#   A6A*A6*A3	/A66A?N)r   r   r   r   r  r  r!  r&  r,  r.  r0  r3  r   r   r   r  r  ~  s/    98:	B: 
H5#-,r   r  c                   :    e Zd ZdZd Zd Zd Zd Zd Zd Z	d Z
y	)
TestFormatPersonaTagu$   format_persona_tag 함수 테스트.c                    t               }dddd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                  |      d
z  }dd|iz  }t        t        j                  |            dx}}y)ue   team과 role이 모두 있으면 '⚡ 헤르메스(개발1팀/개발1팀장)' 형태를 반환한다.r   r(  r     개발1팀장r,   r:   r   u*   ⚡ 헤르메스(개발1팀/개발1팀장)rZ   r   r   r   r   r   N
rG   format_persona_tagrj   rk   rl   rm   rn   ro   rp   rq   rT   rF   personas_datar   r   rx   r   r   s           r   test_format_with_team_and_rolez3TestFormatPersonaTag.test_format_with_team_and_role  s    _&$'
 &&x?EEvEEEEEvEEEEEEEvEEEvEEEEEEEEEEEr   c                    t               }dddd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                  |      d
z  }dd|iz  }t        t        j                  |            dx}}y)uS   team이 없고 role만 있으면 '이모지 이름(역할)' 형태를 반환한다.r   r(  r   r7  r8  u   ⚡ 헤르메스(개발1팀장)rZ   r   r   r   r   r   Nr9  r;  s           r   test_format_with_role_onlyz/TestFormatPersonaTag.test_format_with_role_only  s    _&'
 &&x?::v:::::v:::::::v:::v:::::::::::r   c                    t               }dddddi}|j                  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}}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<   EMOJI_MAP에 없는 키는 '💬' 이모지를 사용한다.unknown_botu   알수없는봇r   u   봇r8  u   💬zLassert %(py6)s
{%(py6)s = %(py2)s
{%(py2)s = %(py0)s.startswith
}(%(py4)s)
}r   )r]   r^   r_   r`   Nr   r   r   r   r   )rG   r:  
startswithrl   rm   rj   rn   ro   rp   rq   rk   )rT   rF   r<  r   rx   ry   rz   r   r   r   r   r   s               r   .test_format_unknown_persona_uses_default_emojizCTestFormatPersonaTag.test_format_unknown_persona_uses_default_emoji  s   _)&
 &&}mD  (( ((((((((v(((v((( ((((((((((((( * F**** F*** ******F***F*******r   c                    t               }i }|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}t        |      }d}||kD  }|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)uU   personas_data에 없는 키를 처리할 때 키 자체를 이름으로 사용한다.nonexistentr   r   r   ri   r   Nr   r   r   r6   r   r   ra   )rG   r:  r   ri   rl   rm   rj   rn   ro   rp   rq   r6   rk   )rT   rF   r<  r   ry   r   r   rz   r   r   r   s              r   test_format_missing_persona_keyz4TestFormatPersonaTag.test_format_missing_persona_key   s3   _&&}mD&#&&&&&&&&z&&&z&&&&&&&&&&&&&&&&&#&&&#&&&&&&&&&&6{Q{Q{Qss66{Qr   c                    t               }dddd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                  |      d
z  }dd|iz  }t        t        j                  |            dx}}y)u(   오딘의 태그 포맷을 검증한다.r      오딘r     개발2팀장r8  u(   👁️ 오딘(개발2팀/개발2팀장)rZ   r   r   r   r   r   Nr9  r;  s           r   test_format_odin_tagz)TestFormatPersonaTag.test_format_odin_tag)  s    _ $'
 &&v}=CCvCCCCCvCCCCCCCvCCCvCCCCCCCCCCCr   c                    t               }dddd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                  |      d
z  }dd|iz  }t        t        j                  |            dx}}y)u(   로키의 태그 포맷을 검증한다.r   r)  r  u   보안팀 리더r8  u'   🎭 로키(보안팀/보안팀 리더)rZ   r   r   r   r   r   Nr9  r;  s           r   test_format_loki_tagz)TestFormatPersonaTag.test_format_loki_tag6  s    _ #*
 &&v}=BBvBBBBBvBBBBBBBvBBBvBBBBBBBBBBBr   c                    t               }t        j                         st        j                  d       |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
)u5   실제 조직도 데이터로 포맷을 검증한다.r   r   r(  r   r   r   r   r   r   Nr  r7  )rG   r/   r0   r   r   r   r:  rj   rk   ro   rl   rm   rn   rp   rq   )rT   rF   r<  r   r   r   r   r   s           r   test_format_with_real_org_dataz3TestFormatPersonaTag.test_format_with_real_org_dataC  sM   _!((*KKMN113&&x?'~''''~'''~''''''''''''''''%|v%%%%|v%%%|%%%%%%v%%%v%%%%%%%(&((((&(((((((((&(((&(((((((r   N)r   r   r   r   r=  r?  rC  rF  rJ  rL  rN  r   r   r   r5  r5    s+    .F;+DC
)r   r5  c                   .    e Zd ZdZd Zd Zd Zd Zd Zy)TestEmojiMapu   EMOJI_MAP 상수 테스트.c                    t               }h d}|j                  }|j                  }|j                  } |       }t	        |      } ||      }|sgddt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      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                  |      t        j                  |      d	z  }	t        t        j                  |	            dx}x}x}x}x}}y)u;   주요 페르소나가 EMOJI_MAP에 있는지 확인한다.>   r  r   isisr   r  r   r   argosfreyahorusr  mimirsobekr   r  r   r   r   heimdallzassert %(py14)s
{%(py14)s = %(py2)s
{%(py2)s = %(py0)s.issubset
}(%(py12)s
{%(py12)s = %(py3)s(%(py10)s
{%(py10)s = %(py8)s
{%(py8)s = %(py6)s
{%(py6)s = %(py4)s.EMOJI_MAP
}.keys
}()
})
})
}r   r4   rF   r   N)rG   r  	EMOJI_MAPrD   r4   rl   rm   rj   rn   ro   rp   rq   r  s
             r   test_known_personas_have_emojiz+TestEmojiMap.test_known_personas_have_emojiX  s   _
* %%?",,?,*;*;?*;*=?c*=&>?%&>????????}???}???%??????c???c??????"???"???,???*;???*=???&>???????????r   c                 ,   t               }|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)	u)   헤르메스의 이모지가 '⚡'이다.r   u   ⚡rZ   r   r   r   r`   NrG   rY  rj   rk   ro   rp   rq   rT   rF   r   ry   r   r   r   s          r   test_hermes_emojizTestEmojiMap.test_hermes_emojir  s_    _||H%..%....%...%..........r   c                 ,   t               }|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)	u$   로키의 이모지가 '🎭'이다.r   u   🎭rZ   r   r   r   r`   Nr\  r]  s          r   test_loki_emojizTestEmojiMap.test_loki_emojiw  s_    _||F#-v-#v----#v---#---v-------r   c                    t               }|j                  }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t        j                  |      dt	        j
                         v st        j                  t              rt        j                  t              ndt        j                  |      dz  }t        t        j                  |            dx}}y)u'   EMOJI_MAP이 딕셔너리 타입이다.zTassert %(py6)s
{%(py6)s = %(py0)s(%(py3)s
{%(py3)s = %(py1)s.EMOJI_MAP
}, %(py4)s)
}r   rF   rO   r]   r   r   r_   r`   N)rG   rY  r   rO   rl   rm   rj   rn   ro   rp   rq   rT   rF   r   rz   r   s        r   test_emoji_map_is_dictz#TestEmojiMap.test_emoji_map_is_dict|  s    _,,-z,--------z---z------"---"---,-------------------r   c                 &   t               }|j                  j                         D ]i  \  }}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}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}}l y)u+   EMOJI_MAP의 모든 값이 문자열이다.u$   의 이모지가 문자열이 아님z7
>assert %(py4)s
{%(py4)s = %(py0)s(%(py1)s, %(py2)s)
}r   r  ri   r   Nr   r   r   r6   r   u   의 이모지가 빈 문자열z
>assert %(py8)sra   )rG   rY  rg   r   ri   rj   r   rl   rm   rn   ro   rp   rq   r6   rk   )rT   rF   rE   r  ry   r   r   rz   r   r   r   s              r   !test_emoji_map_values_are_stringsz.TestEmojiMap.test_emoji_map_values_are_strings  sd   _,,,,. 	JJCeS)W)WWcU2V+WWWWWWW:WWW:WWWWWWeWWWeWWWWWWSWWWSWWW)WWWWWWu:II:>III:IIIIII3III3IIIIIIuIIIuIII:IIIIIIcU*H#IIIIIIII	Jr   N)	r   r   r   r   rZ  r^  r`  rd  rf  r   r   r   rP  rP  U  s     %@4/
.
.
Jr   rP  c                   "    e Zd ZdZd Zd Zd Zy)TestReadTriggeru   read_trigger 함수 테스트.c                    t               }|dz  }t        j                  |dt        |            5  |j	                         }ddd       d}|u }|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# 1 sw Y   xY w)
u3   트리거 파일이 없으면 None을 반환한다.group_chat_trigger.jsonTRIGGER_FILENr   z%(py0)s is %(py3)sr   r   r   r   )rG   r   rh   ri   read_triggerrj   rk   rl   rm   rn   ro   rp   rq   	rT   rr   rF   r#  r   r   rx   r   r   s	            r   "test_returns_none_when_file_absentz2TestReadTrigger.test_returns_none_when_file_absent  s    _66\\"nc'l; 	'__&F	' v~vvv	' 	'   C//C8c                    t               }ddd}|dz  }|j                  t        j                  |d      d       t	        j
                  |d	t        |            5  |j                         }d
d
d
       |k(  }|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
}|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}	}
y
# 1 sw Y   xY w)u(   트리거 파일을 읽고 삭제한다.start   배포 논의)actiontopicrj  Fr   r   r   rk  NrZ   )z%(py0)s == %(py2)sr   trigger_data)r]   r^   zassert %(py4)sr_   Eassert not %(py4)s
{%(py4)s = %(py2)s
{%(py2)s = %(py0)s.exists
}()
}trigger_filer   )rG   re   r2   r   r   rh   ri   rm  rj   rk   rl   rm   rn   ro   rp   rq   r0   )rT   rr   rF   rv  rx  r   rx   @py_format3r   ry   rz   r   s               r   test_reads_and_deletes_filez+TestReadTrigger.test_reads_and_deletes_file  sD   _")OD";;

<e LW^_\\"nc,.?@ 	'__&F	' %%%%v%%%%%%v%%%v%%%%%%%%%%%%%%%%&&(&(((((((((((<(((<(((&((((((((((	' 	's   G11G;c                    t               }|dz  }|j                  dd       t        j                  |dt	        |            5  |j                         }ddd       d}|u }|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# 1 sw Y   xY w)uB   트리거 파일의 JSON이 깨져 있으면 None을 반환한다.rj  z{broken json}r   r   rk  Nr   rl  r   r   r   r   )rG   re   r   rh   ri   rm  rj   rk   rl   rm   rn   ro   rp   rq   )	rT   rr   rF   rx  r   r   rx   r   r   s	            r   !test_returns_none_on_invalid_jsonz1TestReadTrigger.test_returns_none_on_invalid_json  s    _";;'B\\"nc,.?@ 	'__&F	' v~vvv	' 	's   DDN)r   r   r   r   ro  rz  r|  r   r   r   rh  rh    s    ()	r   rh  c                   .    e Zd ZdZd Zd Zd Zd Zd Zy)TestDumpAndLoadSessionu-   dump_session / load_session 함수 테스트.c           
      d   t               }|dz  }ddddgdg ddd	d
dd}t        j                  |dt        |            5  |j	                  |       ddd       |j
                  } |       }|sddt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      dz  }t        t        j                  |            dx}}t        j                  |j                  d            }|d   }	d}|	|k(  }
|
slt        j                   d|
fd|	|f      t        j                  |	      t        j                  |      dz  }dd|iz  }t        t        j                  |            dx}	x}
}|d   }	d}|	|u }
|
slt        j                   d|
fd|	|f      t        j                  |	      t        j                  |      dz  }dd|iz  }t        t        j                  |            dx}	x}
}y# 1 sw Y   xY w)u@   dump_session은 세션 데이터를 JSON 파일로 저장한다.group_chat_session.jsonT   테스트 주제r   r   123   @TA         r   r   r$   ru  personaschat_idhistorylast_activity
auto_turnsspeak_countsSESSION_FILENzAassert %(py4)s
{%(py4)s = %(py2)s
{%(py2)s = %(py0)s.exists
}()
}session_filer   r   r   ru  rZ   r   r   r   r`   r$   r   )z%(py1)s is %(py4)s)rG   r   rh   ri   dump_sessionr0   rl   rm   rj   rn   ro   rp   rq   r2   loads	read_textrk   )rT   rr   rF   r  session_datarx   ry   r   loadedr   r   r   s               r   test_dump_creates_filez-TestDumpAndLoadSession.test_dump_creates_file  s   _";;'!8,)'(A6	
 \\"nc,.?@ 	*OOL)	* ""$"$$$$$$$$|$$$|$$$"$$$$$$$$$$L22G2DEg4"44"44444"4444444"44444444h'4'4''''4''''''4'''''''	* 	*s    H%%H/c           	      d   t               }|dz  }dddgdg ddddid}|j                  t        j                  |d	
      d       t	        j
                  |dt        |            5  |j                         }ddd       d}|u}|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   }
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   0xY w)uD   active=True인 세션 파일이 있으면 데이터를 반환한다.r  Tu   복구 테스트r   456r  r   r  Fr   r   r   r  Nis notz%(py0)s is not %(py3)sr   r   r   r   ru  rZ   r   r   r   r`   rG   re   r2   r   r   rh   ri   load_sessionrj   rk   rl   rm   rn   ro   rp   rq   )rT   rr   rF   r  r  r   r   rx   r   r   r   ry   r   r   s                 r   (test_load_session_returns_active_sessionz?TestDumpAndLoadSession.test_load_session_returns_active_session  s=   _";;')#QK	
 	

<e LW^_\\"nc,.?@ 	'__&F	' "!vT!!!!vT!!!!!!v!!!v!!!T!!!!!!!g4"44"44444"4444444"44444444		' 	's   &F%%F/c                 P   t               }|dz  }ddd}|j                  t        j                  |d      d       t	        j
                  |dt        |            5  |j                         }d	d	d	       d	}|u }|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	# 1 sw Y   xY w)u6   active=False인 세션 파일은 None을 반환한다.r  Fu   종료된 세션)r$   ru  r   r   r   r  Nr   rl  r   r   r   r   r  )
rT   rr   rF   r  r  r   r   rx   r   r   s
             r   ,test_load_session_returns_none_when_inactivezCTestDumpAndLoadSession.test_load_session_returns_none_when_inactive  s    _";;"'2DE

<e LW^_\\"nc,.?@ 	'__&F	' v~vvv	' 	's   DD%c                    t               }|dz  }t        j                  |dt        |            5  |j	                         }ddd       d}|u }|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# 1 sw Y   xY w)
u0   세션 파일이 없으면 None을 반환한다.no_session.jsonr  Nr   rl  r   r   r   r   )rG   r   rh   ri   r  rj   rk   rl   rm   rn   ro   rp   rq   rn  s	            r   0test_load_session_returns_none_when_file_missingzGTestDumpAndLoadSession.test_load_session_returns_none_when_file_missing  s    _..\\"nc'l; 	'__&F	' v~vvv	' 	'rp  c           
         t               }|dz  }ddddgddddd	gd
ddddd}t        j                  |dt        |            5  |j	                  |       |j                         }ddd       d}|u}|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   }
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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   }t        |      }d}||k(  }|st        j                  d|fd||f      d t        j                         v st        j                  t              rt        j                  t              nd t        j                  |      t        j                  |      t        j                  |      d!z  }d"d#|iz  }t        t        j                  |            dx}x}x}}y# 1 sw Y   xY w)$u/   dump_session → load_session 왕복 테스트.r  Tu   왕복 테스트r   r   789r)     안녕)speakerspeaker_namecontentg  tTAr  r  )r   r   r  r  Nr  r  r   r   r   r   ru  rZ   r   r   r   r`   r  r  z0%(py4)s
{%(py4)s = %(py0)s(%(py2)s)
} == %(py7)sr6   r]   r^   r_   r   assert %(py9)spy9)rG   r   rh   ri   r  r  rj   rk   rl   rm   rn   ro   rp   rq   r6   )rT   rr   rF   r  r  r   r   rx   r   r   r   ry   r   r   @py_assert6rz   r   @py_format10s                     r   test_dump_and_load_roundtripz3TestDumpAndLoadSession.test_dump_and_load_roundtrip   s   _";;'($*HQYZ[)%&2	
 \\"nc,.?@ 	'OOL)__&F	' "!vT!!!!vT!!!!!!v!!!v!!!T!!!!!!!g4"44"44444"4444444"44444444j!5ff%55!%55555!%5555!555%55555555)$*s$%**%****%******s***s***$***%**********	' 	's   "LLN)	r   r   r   r   r  r  r  r  r  r   r   r   r~  r~    s    7(.5,
+r   r~  c                      t               S )u+   group_chat 모듈 (픽스처에서 공유).)rG   r   r   r   	gc_moduler    s     ?r   c                 @    | j                  d| j                        }|S )u   격리된 GroupChatSession 인스턴스.

    DEFAULT_PERSONAS 구조 (team, persona_desc 필드 포함, emoji 필드 없음)를 사용한다.
    
fake-tokenr   r<  )GroupChatSessionr   )r  sesss     r   session_objr  $  s,     %%00 & D Kr   c                   @    e Zd ZdZd Zd Zd Zd Zd Zd Z	d Z
d	 Zy
)TestGroupChatSessionStartu2   GroupChatSession.start 입장 시퀀스 테스트.c                    ddddgdd}t        j                  |d      5 }t        d      5  t        d	d
      5  |j                  |       ddd       ddd       d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                  |      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)ui   start()는 입장 안내 1회 + 페르소나별 1회 + 시작 안내 1회 = N+2 회 send를 호출한다.rr  u   배포 전략r   r   999rt  ru  r  r  sendgroup_chat.dump_sessiongroup_chat.call_claude   테스트 인사입니다.r
   N   rZ   )z2%(py2)s
{%(py2)s = %(py0)s.call_count
} == %(py5)s	mock_sendr]   r^   r   r   r   )r   rh   rr  
call_countrj   rk   rl   rm   rn   ro   rp   rq   )	rT   r  triggerr  rx   r   ry   r   r   s	            r   -test_start_calls_send_correct_number_of_timeszGTestGroupChatSessionStart.test_start_calls_send_correct_number_of_times9  s    $!8,	
 \\+v. 	/)01 /3B^_ /%%g.//	/ ##(q(#q((((#q((((((y(((y(((#(((q(((((((	/ // /	/ 	/s9   ED9D-D9E-D62D99E	>EEc                    dddgdd}t        j                  |d      5  t        d      5  t        dd	
      5  |j                  |       ddd       ddd       ddd       |j                  }d}||u }|st	        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  }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)u%   start() 후 active가 True가 된다.rr  r  r   100r  r  r  r  r  r
   NTr   z.%(py2)s
{%(py2)s = %(py0)s.active
} is %(py5)sr  r  r   r   )r   rh   rr  r$   rj   rk   rl   rm   rn   ro   rp   rq   rT   r  r  rx   r   ry   r   r   s           r   test_start_sets_active_truez5TestGroupChatSessionStart.test_start_sets_active_trueI  s     '	
 \\+v. 	/01 /3B^_ /%%g.//	/
 !!)T)!T))))!T)))))){))){)))!)))T)))))))/ // /	/ 	/s9   ED7D+D7E+D40D77E 	<EEc                    dg dd}t        j                  |d      5  t        d      5  t        dd      5  |j                  |       d	d	d	       d	d	d	       d	d	d	       |j                  }d
d
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                  |      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)u4   start() 후 speak_counts가 0으로 초기화된다.u   발화 카운트 초기화r   r   r   ru  r  r  r  r  r  r
   Nr   rZ   )z4%(py2)s
{%(py2)s = %(py0)s.speak_counts
} == %(py5)sr  r  r   r   )r   rh   rr  r  rj   rk   rl   rm   rn   ro   rp   rq   r  s           r   #test_start_initializes_speak_countsz=TestGroupChatSessionStart.test_start_initializes_speak_countsX  s    24
 \\+v. 	/01 /3B^_ /%%g.//	/
 ''Pa1a+PP'+PPPPP'+PPPPPPP{PPP{PPP'PPP+PPPPPPPP/ // /	/ 	/s9   ED;D/
D;E/D84D;;E	 EEc                    ddgdd}t        j                  |d      5  t        d      5  t        dd	      5  |j                  |       d
d
d
       d
d
d
       d
d
d
       |j                  }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                  |      t        j                  |      dz  }dd|iz  }t        t        j                  |            d
x}x}x}}|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                  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   hxY w# 1 sw Y   mxY w# 1 sw Y   rxY w)u7   user_message가 있으면 히스토리에 추가된다.u   프로젝트 킥오프r   u   안녕하세요 여러분!)ru  r  user_messager  r  r  r  r
   Nr  rZ   zM%(py5)s
{%(py5)s = %(py0)s(%(py3)s
{%(py3)s = %(py1)s.history
})
} == %(py8)sr6   r  r]   r   r   r   ra   assert %(py10)sr  r   r  r   r   r   r`   r  user)r   rh   rr  r  r6   rj   rk   rl   rm   rn   ro   rp   rq   )rT   r  r  r   r   r{   r  r   @py_format11r   ry   r   r   s                r   ,test_start_with_user_message_adds_to_historyzFTestGroupChatSessionStart.test_start_with_user_message_adds_to_historye  s    .!
8

 \\+v. 	/01 /3B^_ /%%g.//	/
 &&,s&',1,'1,,,,'1,,,,,,s,,,s,,,,,,;,,,;,,,&,,,',,,1,,,,,,,""1%i0P4PP04PPPPP04PPPP0PPP4PPPPPPPP""1%i0:F:0F::::0F:::0:::F:::::::/ // /	/ 	/s9   K
J=J0
J=K
0J:5J==K	K

Kc                    ddgd}g t        j                  |dfd      5  t        d      5  t        dd	
      5  |j                  |       ddd       ddd       dd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}}y# 1 sw Y   xY w# 1 sw Y   xY w# 1 sw Y   xY w)uH   start()의 첫 번째 send 메시지는 '잠깐요' 안내 메시지다.u   긴급 회의r   r  r  c                 &    j                  |       S Nappendtsent_messagess    r   <lambda>zLTestGroupChatSessionStart.test_start_first_message_content.<locals>.<lambda>}      ]EYEYZ[E\ r   side_effectr  r  r  r
   Nu	   잠깐요r   r   z%(py1)s in %(py4)sr   r   r`   )r   rh   rr  rj   rk   ro   rp   rq   )	rT   r  r  r   ry   r   r   r   r  s	           @r    test_start_first_message_contentz:TestGroupChatSessionStart.test_start_first_message_contentu  s     %
 \\+v;\] 	/01 /3B^_ /%%g.//	/
 .mA..{.....{....{.........../ // /	/ 	/s9   DC5C)C5D)C2.C55C>	:DD
c                   	 g d}d|d}g 	t        j                  |d	fd      5  t        d      5  t        dd	
      5  |j                  |       ddd       ddd       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}}y# 1 sw Y   jxY w# 1 sw Y   oxY w# 1 sw Y   txY w)uI   start()의 마지막 send 메시지는 참여 인원 수를 포함한다.r  u
   팀 회의r  r  c                 &    j                  |       S r  r  r  s    r   r  zYTestGroupChatSessionStart.test_start_last_message_contains_member_count.<locals>.<lambda>  r  r   r  r  r  r  r
   Nr   3r   r   last_msgr   r   r   u   명r   rh   rr  rj   rk   ro   rl   rm   rn   rp   rq   )
rT   r  r  r  r  r   r   r   r   r  s
            @r   -test_start_last_message_contains_member_countzGTestGroupChatSessionStart.test_start_last_message_contains_member_count  sG   /(h?\\+v;\] 	/01 /3B^_ /%%g.//	/
 !$shshshh u    u   u                / // /	/ 	/s:   GG	F<G	G<GG		G	GG c                    ddgd}g t        j                  |dfd      5  t        d      5  t        dt        d	            5  |j                  |       d
d
d
       d
d
d
       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
# 1 sw Y   xY w# 1 sw Y   xY w# 1 sw Y   xY w)u_   CLI 오류 시 폴백 인사('잘 부탁드립니다.')가 포함된 메시지가 전송된다.u   폴백 테스트r   r  r  c                 &    j                  |       S r  r  r  s    r   r  zWTestGroupChatSessionStart.test_start_api_error_uses_fallback_greeting.<locals>.<lambda>  r  r   r  r  r  
   CLI 오류Nr  u   잘 부탁드립니다.r   r   persona_msgr   r   r   )r   rh   r   rr  rj   rk   ro   rl   rm   rn   rp   rq   	rT   r  r  r  r   r   r   r   r  s	           @r   +test_start_api_error_uses_fallback_greetingzETestGroupChatSessionStart.test_start_api_error_uses_fallback_greeting  s     (
 \\+v;\] 	/01 /3lA[\ /%%g.//	/ $A&(7(K7777(K777(777777K777K7777777/ // /	/ 	/s:   D5D)DD)"D5D&"D))D2	.D55D>c                    ddgd}g t        j                  |dfd      5  t        d      5  t        dd	
      5  |j                  |       ddd       ddd       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}}y# 1 sw Y   jxY w# 1 sw Y   oxY w# 1 sw Y   txY w)uM   페르소나 입장 메시지가 '이름(팀/역할)' 포맷을 포함한다.u   포맷 확인r   r  r  c                 &    j                  |       S r  r  r  s    r   r  zZTestGroupChatSessionStart.test_start_persona_message_contains_new_format.<locals>.<lambda>  r  r   r  r  r  r  r
   Nr  r(  r   r   r  r   r   r   r  r  r  s	           @r   .test_start_persona_message_contains_new_formatzHTestGroupChatSessionStart.test_start_persona_message_contains_new_format  sL    %!

 \\+v;\] 	/01 /3B^_ /%%g.//	/ $A&,~,,,,~,,,~,,,,,,,,,,,,,,,,*|{****|{***|******{***{*******/ // /	/ 	/s9   GGF9GG9G>GG	GGN)r   r   r   r   r  r  r  r  r  r  r  r  r   r   r   r  r  6  s.    <) *Q; /!8"+r   r  c                   <    e Zd ZdZd
dZd Zd Zd Zd Zd Z	d	 Z
y)TestGroupChatSessionEndu0   GroupChatSession.end 퇴장 시퀀스 테스트.Nc                 ~    |ddg}d|_         d|_        ||_        |D ci c]  }|d c}|_        g |_        yc c}w )u)   테스트용 세션 상태 준비 헬퍼.Nr   r   Tu   종료 테스트r  )r$   ru  r  r  r  )rT   r  r  ps       r   _preparez TestGroupChatSessionEnd._prepare  sN     (+H!.'2:#;QAqD#;   $<s   
:c           	         | j                  |       |dz  }t        j                  |d      5  t        dt        |            5  t        d      5  t        dd      5  |j	                  d       d	d	d	       d	d	d	       d	d	d	       d	d	d	       |j
                  }d
}||u }|st        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  }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# 1 sw Y   xY w)u$   end() 후 active가 False가 된다.r  r  group_chat.SESSION_FILEgroup_chat.save_session_logr     수고하셨습니다.r
   	user_exitNFr   r  r  r  r   r   )r  r   rh   ri   endr$   rj   rk   rl   rm   rn   ro   rp   rq   	rT   r  rr   r  rx   r   ry   r   r   s	            r   test_end_sets_active_falsez2TestGroupChatSessionEnd.test_end_sets_active_false  s    k"";;\\+v. 	50#l2CD 589 57F^_ 5#4555	5 !!*U*!U****!U******{***{***!***U*******5 55 55 5	5 	5sS   E;E/E#E	/E#7E/?E;E E##E,(E//E8	4E;;Fc           	      &   | j                  |       |dz  }|j                  t        j                  ddi      d       t	        j
                  |d      5  t	        dt        |            5  t	        d      5  t	        d	d
      5  |j                  d       ddd       ddd       ddd       ddd       |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}}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)u(   end() 후 세션 파일이 삭제된다.r  r$   Tr   r   r  r  r  r  r  r
   r  Nrw  r  r   )r  re   r2   r   r   rh   ri   r   r0   rl   rm   rj   rn   ro   rp   rq   )rT   r  rr   r  rx   ry   rz   r   s           r   test_end_removes_session_filez5TestGroupChatSessionEnd.test_end_removes_session_file  s6   k"";;

Hd+; <wO\\+v. 	50#l2CD 589 57F^_ 5#4555	5  &&(&(((((((((((<(((<(((&((((((((((5 55 55 5	5 	5sT   F+E;7E/E#	E/E;'F#E,(E//E84E;;F	 FFc           	        
 g d}| j                  ||       |dz  }g 
t        j                  |d
fd      5  t        dt        |            5  t        d      5  t        dd	
      5  |j	                  d       d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   3xY w# 1 sw Y   8xY w# 1 sw Y   =xY w# 1 sw Y   BxY w)u`   end()는 페르소나별 작별 인사 1회 + 종료 요약 1회 = N+1 회 send를 호출한다.r  r  r  c                 &    j                  |       S r  r  r  
sent_callss    r   r  zZTestGroupChatSessionEnd.test_end_sends_farewell_per_persona_plus_summary.<locals>.<lambda>      ZEVEVWXEY r   r  r  r  r  r  r
   r  Nr  rZ   )z0%(py3)s
{%(py3)s = %(py0)s(%(py1)s)
} == %(py6)sr6   r  r   r   ra   )r  r   rh   ri   r   r6   rj   rk   rl   rm   rn   ro   rp   rq   )rT   r  rr   r  r  r   rz   r   r   r   r  s             @r   0test_end_sends_farewell_per_persona_plus_summaryzHTestGroupChatSessionEnd.test_end_sends_farewell_per_persona_plus_summary  sJ   /k8,"33
\\+v;YZ 	50#l2CD 589 57F^_ 5#4555	5 :#!#!####!######s###s######:###:######!#######	5 55 55 5	5 	5sS   GGF:*F-	<F:GG-F72F::G?GG	GGc           	        	 | j                  |dg       d|_        g 	|dz  }t        j                  |d	fd      5  t        dt	        |            5  t        d      5  t        d	d
      5  |j                  d       ddd       ddd       ddd       dd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}}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)u:   end()의 마지막 메시지에는 주제가 포함된다.r   u   UI 개선 논의r  r  c                 &    j                  |       S r  r  r  s    r   r  zOTestGroupChatSessionEnd.test_end_final_message_contains_topic.<locals>.<lambda>  r	  r   r  r  r  r  r  r
   r  Nr   r   r  r   r   r`   )r  ru  r   rh   ri   r   rj   rk   ro   rp   rq   )
rT   r  rr   r  r   ry   r   r   r   r  s
            @r   %test_end_final_message_contains_topicz=TestGroupChatSessionEnd.test_end_final_message_contains_topic  s   kF8,.
"33\\+v;YZ 	50#l2CD 589 57F^_ 5#4555	5 "3Z^3!^3333!^333!333^33333335 55 55 5	5 	5sS   ED8 D,.D 	 D,D8E D)%D,,D51D88E	=EEc           	         | j                  |dg       g |dz  }t        j                  |dfd      5  t        dt        |            5  t        d      5  t        dt	        d	            5  |j                  d
       ddd       ddd       ddd       ddd       |j                  }d}||u }|st        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  }t        t        j                  |            dx}x}}d   }	d}
|
|	v }|st        j                  d|fd|
|	f      t        j                  |
      dt        j                         v st        j                  |	      rt        j                  |	      nddz  }dd|iz  }t        t        j                  |            dx}
}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)u_   CLI 오류 시 폴백 작별 인사('수고하셨습니다, 제이회장님.')가 전송된다.r   r  r  c                 &    j                  |       S r  r  r  s    r   r  zUTestGroupChatSessionEnd.test_end_api_failure_uses_fallback_farewell.<locals>.<lambda>	  r	  r   r  r  r  r  r  r  NFr   r  r  r  r   r   r   u   수고하셨습니다r   r   farewell_msgr   r   r   )r  r   rh   ri   r   r   r$   rj   rk   rl   rm   rn   ro   rp   rq   )rT   r  rr   r  rx   r   ry   r   r   r  r   r   r   r  s                @r   +test_end_api_failure_uses_fallback_farewellzCTestGroupChatSessionEnd.test_end_api_failure_uses_fallback_farewell  s   kF8,
"33\\+v;YZ 	50#l2CD 589 57\R^E_` 5#4555	5 !!*U*!U****!U******{***{***!***U*******!!}&6&,6666&,666&666666,666,66666665 55 55 5	5 	5sS   H;H.H!0H	H!
H.H;HH!!H+&H..H8	3H;;Ic           	      :  	 | j                  |dg       g 	|dz  }t        j                  |d	fd      5  t        dt        |            5  t        d      5  t        dd	
      5  |j	                  d       ddd       ddd       ddd       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}}y# 1 sw Y   rxY w# 1 sw Y   wxY w# 1 sw Y   |xY w# 1 sw Y   xY w)uP   end()의 작별 인사 메시지가 '이름(팀/역할)' 포맷을 포함한다.r   r  r  c                 &    j                  |       S r  r  r  s    r   r  zWTestGroupChatSessionEnd.test_end_farewell_message_contains_new_format.<locals>.<lambda>  r	  r   r  r  r  r  r  r
   r  Nr   r(  r   r   r  r   r   r   r  )r  r   rh   ri   r   rj   rk   ro   rl   rm   rn   rp   rq   )
rT   r  rr   r  r  r   r   r   r   r  s
            @r   -test_end_farewell_message_contains_new_formatzETestGroupChatSessionEnd.test_end_farewell_message_contains_new_format  sv   kH:.
"33\\+v;YZ 	50#l2CD 589 57F^_ 5#4555	5 "!}-~----~---~----------------+||++++||+++|++++++|+++|+++++++5 55 55 5	5 	5sS   HHG6'G)	9G6H	H)G3.G66H ;HH	HHr  )r   r   r   r   r  r  r  r
  r  r  r  r   r   r   r  r    s(    :!+)$ 47 ,r   r  c                   .    e Zd ZdZd Zd Zd Zd Zd Zy)TestAddUserInputu*   GroupChatSession.add_user_input 테스트.c                    d|_         |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                  |      t        j                  |      dz  }dd	|iz  }t        t        j                  |            d
x}x}}y
)u?   add_user_input() 호출 시 auto_turns가 0으로 리셋된다.   u   새 메시지r   rZ   z2%(py2)s
{%(py2)s = %(py0)s.auto_turns
} == %(py5)sr  r  r   r   N
r  add_user_inputrj   rk   rl   rm   rn   ro   rp   rq   rT   r  rx   r   ry   r   r   s          r   test_resets_auto_turnsz'TestAddUserInput.test_resets_auto_turns-  s    !"""?3%%**%****%******{***{***%**********r   c                    g |_         |j                  d       |j                   }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                  |      t        j                  |      dz  }dd	|iz  }t        t        j                  |            d
x}x}x}}|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                   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   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   add_user_input() 호출 시 히스토리에 메시지가 추가된다.u   테스트 입력r  rZ   r  r6   r  r  r  r  Nr   r  r   r   r   r`   r  r  r  u   제이회장님r  r  r6   rj   rk   rl   rm   rn   ro   rp   rq   rT   r  r   r   r{   r  r   r  r   ry   r   r   s               r   test_appends_to_historyz(TestAddUserInput.test_appends_to_history3  s    ""#56&&,s&',1,'1,,,,'1,,,,,,s,,,s,,,,,,;,,,;,,,&,,,',,,1,,,,,,,""1%i0F4FF04FFFFF04FFFF0FFF4FFFFFFFF""1%i0:F:0F::::0F:::0:::F:::::::""1%n5J9JJ59JJJJJ59JJJJ5JJJ9JJJJJJJJr   c                 >   d|_         |j                  d       |j                   }d}||kD  }|st        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  }t        t        j                  |            d	x}x}}|j                   }t        j                  } |       }||k  }|s
t        j                  d
|fd||f      dt	        j
                         v st        j                  |      rt        j                  |      ndt        j                  |      dt	        j
                         v st        j                  t              rt        j                  t              ndt        j                  |      t        j                  |      dz  }	dd|	iz  }
t        t        j                  |
            d	x}x}x}}y	)u:   add_user_input() 호출 시 last_activity가 갱신된다.g        u   활동 시간 갱신r   )z4%(py2)s
{%(py2)s = %(py0)s.last_activity
} > %(py5)sr  r  r   r   N)<=)zf%(py2)s
{%(py2)s = %(py0)s.last_activity
} <= %(py8)s
{%(py8)s = %(py6)s
{%(py6)s = %(py4)s.time
}()
}time)r]   r^   r_   r`   ra   r  r  )r  r  rj   rk   rl   rm   rn   ro   rp   rq   r$  )rT   r  rx   r   ry   r   r   rz   r{   r   r  s              r   test_updates_last_activityz+TestAddUserInput.test_updates_last_activity<  s2   $'!""#9:((.3.(3....(3......{...{...(...3.......((7DII7IK7(K7777(K777777{777{777(777777D777D777I777K7777777r   c                    g |_         |j                  d       |j                  d       |j                  d       |j                   }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                  |      t        j                  |      d	z  }d
d|iz  }t        t        j                  |            dx}x}x}}|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)u5   여러 번 호출하면 히스토리가 누적된다.u
   첫 번째u
   두 번째u
   세 번째r  rZ   r  r6   r  r  r  r  Nr  r  r   r   r   r`   r  r   s               r   *test_multiple_inputs_accumulate_in_historyz;TestAddUserInput.test_multiple_inputs_accumulate_in_historyC  s9    ""<0""<0""<0&&,s&',1,'1,,,,'1,,,,,,s,,,s,,,,,,;,,,;,,,&,,,',,,1,,,,,,,""1%i0@L@0L@@@@0L@@@0@@@L@@@@@@@r   c                    dD ]  }||_         |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                  |      t        j                  |      dz  }dd	|iz  }t        t        j                  |            d
x}x}} y
)u4   auto_turns가 어떤 값이든 0으로 리셋된다.)r   r     d   u   리셋 확인r   rZ   r  r  r  r   r   Nr  )rT   r  initialrx   r   ry   r   r   s           r   3test_auto_turns_resets_regardless_of_previous_valuezDTestAddUserInput.test_auto_turns_resets_regardless_of_previous_valueL  s    % 	/G%,K"&&7)).Q.)Q....)Q......;...;...)...Q.......	/r   N)	r   r   r   r   r  r!  r%  r'  r,  r   r   r   r  r  *  s     4+K8A/r   r  c                   4    e Zd ZdZd Zd Zd Zd Zd Zd Z	y)	TestSelectNextSpeakeru%   select_next_speaker 함수 테스트.c                    t               }dddd}g d}t        dt        d            5  |j                  |g d	|
      }ddd       d	}|k7  }|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# 1 sw Y   xY w)u=   CLI 오류 시 폴백에서 직전 발화자를 제외한다.r  r  r  r  )r(  	   아테나   토르r  r  r  r   persona_namesr  last_speakerr  N!=)z%(py0)s != %(py3)sr   r   r   r   rG   r   r   select_next_speakerrj   rk   rl   rm   rn   ro   rp   rq   	rT   rF   r  r3  r   r   rx   r   r   s	            r   &test_excludes_last_speaker_in_fallbackz<TestSelectNextSpeaker.test_excludes_last_speaker_in_fallback\  s    _"#q!<?+l9ST 	+++%)	 , F	 "!v!!!!v!!!!!!v!!!v!!!!!!!!!!	 	   C00C9c                    t               }dddd}g d}t        dt        d            5  |j                  |g d	|
      }d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  }dd|iz  }t        t	        j                  |            dx}}y# 1 sw Y   xY w)uN   CLI 오류 폴백에서 발화 횟수가 가장 적은 화자를 선택한다.r  r   r  )r   r   r   )r(  r)  rH  r  r  r  r   r2  Nr   rZ   r   r   r   r   r   r7  r9  s	            r    test_fallback_picks_least_spokenz6TestSelectNextSpeaker.test_fallback_picks_least_spokenm  s    _ #$Q:<+l9ST 	+++%)	 , F	  vvvv	 	r;  c           
         t               }t        dd      5  |j                  g dg ddddd	      }d
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  }dd|iz  }t        t        j                  |            d
x}}y
# 1 sw Y   xY w)uA   CLI가 유효한 JSON을 반환하면 그 결과를 사용한다.r  u<   {"next_speaker": "odin", "reason": "자연스러운 순서"}r
   )r(  rH  r)  r   r  r  )r   r   r   r2  Nr   rZ   r   r   r   r   r   rG   r   r8  rj   rk   rl   rm   rn   ro   rp   rq   rT   rF   r   r   rx   r   r   s          r   !test_uses_api_response_when_validz7TestSelectNextSpeaker.test_uses_api_response_when_valid  s    _+:xy 	++B%()1a@	 , F	  vvvv	 	   C##C,c           	         t               }t        dd      5  |j                  ddgg dddd	
      }d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  }dd|iz  }t        t        j                  |            dx}}y# 1 sw Y   xY w)uN   CLI 응답의 persona_key가 speak_counts에 없으면 폴백을 사용한다.r  u0   {"next_speaker": "zeus", "reason": "없는 키"}r
   r(  r0  r   r  r   r  r2  Nr   rZ   r   r   r   r   r   r?  r@  s          r   ,test_falls_back_when_api_returns_invalid_keyzBTestSelectNextSpeaker.test_falls_back_when_api_returns_invalid_key  s    _+:lm 	++-{;%()Q7	 , F	 "!v!!!!v!!!!!!v!!!v!!!!!!!!!!	 	s   C""C+c                    t               }t        dt        d            5  |j                  dgg dddi      }d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  }dd|iz  }t        t	        j                  |            dx}}y# 1 sw Y   xY w)uT   후보가 한 명뿐이고 직전 발화자와 같으면 그 화자를 선택한다.r  r  r  r(  r   r  r2  NrZ   r   r   r   r   r   r7  r@  s          r   9test_fallback_allows_last_speaker_when_only_one_candidatezOTestSelectNextSpeaker.test_fallback_allows_last_speaker_when_only_one_candidate  s    _+l9ST 	++-.%&]	 , F	 "!v!!!!v!!!!!!v!!!v!!!!!!!!!!	 	s   C))C2c           
         t               }t        dd      5  |j                  g dg ddddd	
      }d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  }dd|iz  }t        t        j                  |            dx}}y# 1 sw Y   xY w)uQ   CLI 응답이 JSON 앞뒤에 텍스트를 포함해도 올바르게 파싱한다.r  ue   다음 발화자를 선택했습니다. {"next_speaker": "thor", "reason": "순서"} 감사합니다.r
   )r(  r1  r0  r   r  r   r  )r   r   r   r2  Nr   rZ   r   r   r   r   r   r?  r@  s          r   +test_api_returns_json_with_surrounding_textzATestSelectNextSpeaker.test_api_returns_json_with_surrounding_text  s    _$ A
 		 ++E%()1B	 , F			  vvvv		 		rB  N)
r   r   r   r   r:  r=  rA  rD  rF  rH  r   r   r   r.  r.  Y  s#    /"" $
 "" r   r.  c                   4    e Zd ZdZd Zd Zd Zd Zd Zd Z	y)	TestMaxAutoTurnsu0   MAX_AUTO_TURNS >= 6 시 대기 동작 테스트.c                    t               }|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                  |      dz  }dd|iz  }t        t        j                  |            dx}x}}y)	u!   MAX_AUTO_TURNS 상수가 6이다.r)  rZ   )z6%(py2)s
{%(py2)s = %(py0)s.MAX_AUTO_TURNS
} == %(py5)srF   r  r   r   N)
rG   MAX_AUTO_TURNSrj   rk   rl   rm   rn   ro   rp   rq   )rT   rF   rx   r   ry   r   r   s          r   #test_max_auto_turns_constant_is_sixz4TestMaxAutoTurns.test_max_auto_turns_constant_is_six  s{    _  %A% A%%%% A%%%%%%r%%%r%%% %%%A%%%%%%%r   c                    d|_         d|_        d|_        dg|_        ddi|_        d|_        t        j                         |_        ddifd}t        d|	      5  t        d
      5 }t        d      5  t        j                  |d      5  t        d      5  t        dd      5  |j                          ddd       ddd       ddd       ddd       ddd       ddd       j                          y# 1 sw Y   BxY w# 1 sw Y   FxY w# 1 sw Y   JxY w# 1 sw Y   NxY w# 1 sw Y   RxY w# 1 sw Y   VxY w)uR   auto_turns >= MAX_AUTO_TURNS 이면 select_next_speaker를 호출하지 않는다.Tr)  u   자동 턴 테스트r   nr   c                  :     dxx   dz  cc<    d   dk(  ry dddS )NrO  r  r   	test_exitrt  reasonr   r  s   r   fake_read_triggerz_TestMaxAutoTurns.test_run_loop_does_not_select_speaker_when_at_limit.<locals>.fake_read_trigger  s+    sOq O#!##{;;r   group_chat.read_triggerr  group_chat.select_next_speakerr  r  
time.sleepr  z/tmp/no_session_auto_turns.jsonN)r$   r  ru  r  r  r4  r$  r  r   rh   run_looprS   rT   r  rU  mock_selectr  s       @r   3test_run_loop_does_not_select_speaker_when_at_limitzDTestMaxAutoTurns.test_run_loop_does_not_select_speaker_when_at_limit  s1   !!"2 (z$,a= #+ $(IIK!1X
	< ,:KL 	778 7K89 7k6: 7"<0 7!&'@Bc!d 7 + 4 4 677777	7 	%%'	7 77 77 77 77 7	7 	7s   D4*D(6DD	D&C87D?D	DD(D48D=DD	D	DDD%!D((D1	-D44D=c                    d|_         d|_        dg|_        ddi|_        d|_        g |_        t        dd      5  t        j                  |d      5  t        d	      5  |j                  d       d
d
d
       d
d
d
       d
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                  |      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)u2   speak() 호출마다 auto_turns가 1 증가한다.Tu   증가 테스트r   r   $group_chat.generate_persona_response   테스트 응답r
   r  r  Nr  rZ   r  r  r  r   r   )r$   ru  r  r  r  r  r   rh   speakrj   rk   rl   rm   rn   ro   rp   rq   r  s          r   #test_auto_turns_increments_on_speakz4TestMaxAutoTurns.test_auto_turns_increments_on_speak  s   !. (z$,a= !" 9HZ[ 	0k62 045 0%%h/00	0
 %%**%****%******{***{***%**********0 00 0	0 	0s;   E)EE0E8E)EEE&	"E))E2c                    d|_         d|_        dg|_        ddi|_        d|_        g |_        t        dd      5  t        j                  |d      5  t        d	      5  t        d
      D ]  }|j                  d        	 ddd       ddd       d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                  |      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)uA   speak()를 여러 번 호출할수록 auto_turns가 누적된다.Tu   누적 테스트r   r   r^  r_  r
   r  r  r  NrZ   r  r  r  r   r   )r$   ru  r  r  r  r  r   rh   ranger`  rj   rk   rl   rm   rn   ro   rp   rq   )rT   r  _rx   r   ry   r   r   s           r   'test_auto_turns_increments_cumulativelyz8TestMaxAutoTurns.test_auto_turns_increments_cumulatively  s/   !. (z$,a= !" 9HZ[ 	4k62 445 4"1X 4#))(3444	4 %%**%****%******{***{***%**********	4 44 4	4 	4s;   E:E."E"E.	E:"E+'E..E7	3E::Fc                    d|_         |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                  |      t        j                  |      dz  }dd	|iz  }t        t        j                  |            d
x}x}}y
)u>   add_user_input() 후에는 auto_turns가 0으로 리셋된다.r)  u    유저가 새로 입력했어요r   rZ   r  r  r  r   r   Nr  r  s          r   -test_add_user_input_resets_auto_turns_to_zeroz>TestMaxAutoTurns.test_add_user_input_resets_auto_turns_to_zero  s    !"""#EF%%**%****%******{***{***%**********r   c                    d|_         d|_        d|_        dg|_        ddi|_        d|_        t        j                         |_        ddifd}t        d|	      5  t        d
d      5 }t        d      5  t        j                  |d      5  t        dd      5  t        d      5  t        d      5  t        dd      5  |j                          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                          y# 1 sw Y   RxY w# 1 sw Y   VxY w# 1 sw Y   ZxY w# 1 sw Y   ^xY w# 1 sw Y   bxY w# 1 sw Y   fxY w# 1 sw Y   jxY w# 1 sw Y   nxY w)uZ   유저 입력으로 auto_turns가 리셋되면 루프가 다시 발화자를 선택한다.Tr)  u   재개 테스트r   rO  r   c                  T     dxx   dz  cc<    d   dk(  rdddS  d   dk(  rddd	S y )
NrO  r  
user_inputu   계속해주세요rt  messager  r   donerR  r   rT  s   r   rU  zdTestMaxAutoTurns.test_run_loop_resumes_after_user_input_resets_auto_turns.<locals>.fake_read_trigger  sC    sOq O#!#".;OPP#!#"'6::r   rV  r  rW  r
   r  r  r^  u   응답r  rX  r  z/tmp/no_session_resume.jsonN)r$   r  ru  r  r  r4  r$  r  r   rh   rY  assert_calledrZ  s       @r   8test_run_loop_resumes_after_user_input_resets_auto_turnszITestMaxAutoTurns.test_run_loop_resumes_after_user_input_resets_auto_turns  ss   !!". (z$,a= #+ $(IIK!1X
	 ,:KL 	?7hO ?S^89 ?k6: ?"#IX`a ?!&'@!A ?%*<%8 !?)./HJg)h %?(3(<(<(>%?!??????	? 	!!#	%? %?!? !?? ?? ?? ?? ?? ?	? 	?s   E8,E,8E E	E)D<5D0D$D0D<#E+E	3E ;E,E8$D-)D00D95D<<EEEE	EE  E)%E,,E5	1E88FN)
r   r   r   r   rM  r\  ra  re  rg  ro  r   r   r   rJ  rJ    s#    :&
(>+ +"+!$r   rJ  c                   F    e Zd ZdZd Zd Zd Zd Zd Zd Z	d Z
d	 Zd
 Zy)TestTelegramPolleru#   TelegramPoller 클래스 테스트.c                 ~   t               }|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                  |      t        j                  |      dz  }dd	|iz  }t        t        j                  |            d
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                  |      dz  }dd	|iz  }t        t        j                  |            d
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                  |      dz  }dd	|iz  }t        t        j                  |            d
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                  |      dz  }dd	|iz  }t        t        j                  |            d
x}x}}y
)uU   __init__이 token, chat_id, base_url, last_update_id를 올바르게 초기화한다.
test-token12345r   r  rZ   )z-%(py2)s
{%(py2)s = %(py0)s.token
} == %(py5)spollerr  r   r   N)z/%(py2)s
{%(py2)s = %(py0)s.chat_id
} == %(py5)sz&https://api.telegram.org/bottest-token)z0%(py2)s
{%(py2)s = %(py0)s.base_url
} == %(py5)sr   z6%(py2)s
{%(py2)s = %(py0)s.last_update_id
} == %(py5)s)rG   TelegramPollerr   rj   rk   rl   rm   rn   ro   rp   rq   r  base_urllast_update_id)rT   rF   rv  rx   r   ry   r   r   s           r   test_init_sets_attributesz,TestTelegramPoller.test_init_sets_attributes7  s   _""w"G||+|+||++++||++++++v+++v+++|+++|+++++++~~((~((((~((((((v(((v(((~((((((((((J"JJ"JJJJJ"JJJJJJJvJJJvJJJJJJ"JJJJJJJJ$$))$))))$))))))v)))v)))$))))))))))r   c                 >   t               }|j                  dd      }t               }ddddidd	d
gd|j                  _        t        d|      5  |j                         }ddd       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# 1 sw Y   xY w)u6   정상 응답 시 메시지 리스트를 반환한다.tokr  ru  Te   r(     u   안녕하세요chattext	update_idrl  r	   r   group_chat.requests.getr
   NrZ   r   messagesr   r   r   rG   rx  r   r2   r   r   get_updatesrj   rk   rl   rm   rn   ro   rp   rq   	rT   rF   rv  	fake_respr  r   rx   r   r   s	            r   ,test_get_updates_returns_messages_on_successz?TestTelegramPoller.test_get_updates_returns_messages_on_success@  s    _""">K	 "%!%s 1 '
	# ,9E 	,))+H	, ...x.....x.......x...x...........	, 	,s   DDc                 <   t               }|j                  dd      }t               }ddddidd	d
gd|j                  _        t        d|      5  |j                         }dd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# 1 sw Y   xY w)uS   chat_id가 불일치하는 메시지는 제외하고 빈 리스트를 반환한다.r}  r  ru  Tf   r(   ix  u   다른 채팅방r  r  r  r  r
   NrZ   r   r  r   r   r   r  r  s	            r   #test_get_updates_filters_by_chat_idz6TestTelegramPoller.test_get_updates_filters_by_chat_idX  s    _""">K	 "%!%s 2 '
	# ,9E 	,))+H	, x2~x2xx2	, 	,s   DDc                 (   t               }|j                  dd      }t               }ddi|j                  _        t        d|      5  |j                         }dd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# 1 sw Y   xY w)u>   API 응답의 ok가 False이면 빈 리스트를 반환한다.r}  r  ru  r	   Fr  r
   NrZ   r   r  r   r   r   r  r  s	            r   +test_get_updates_returns_empty_on_api_errorz>TestTelegramPoller.test_get_updates_returns_empty_on_api_errorp  s    _""">K	'+Um	#,9E 	,))+H	, x2~x2xx2	, 	,s   DDc                     t               }|j                  dd      }t        dt        d            5  |j	                         }dd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# 1 sw Y   xY w)uH   requests.get이 예외를 발생시키면 빈 리스트를 반환한다.r}  r  ru  r  zNetwork errorr  NrZ   r   r  r   r   r   )rG   rx  r   r2  r  rj   rk   rl   rm   rn   ro   rp   rq   )rT   rF   rv  r  r   rx   r   r   s           r   +test_get_updates_returns_empty_on_exceptionz>TestTelegramPoller.test_get_updates_returns_empty_on_exception}  s    _""">,)O:TU 	,))+H	, x2~x2xx2	, 	,s   C44C=c                 "   t               }|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                  |      t        j                  |      dz  }d	d
|iz  }t        t        j                  |            dx}x}}t               }ddddiddddddidddgd|j                  _        t        d|      5  |j                          d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                  |      t        j                  |      dz  }d	d
|iz  }t        t        j                  |            dx}x}}y# 1 sw Y   xY w)uL   get_updates() 호출 후 last_update_id가 최신 update_id로 갱신된다.r}  r  ru  r   rZ   rw  rv  r  r   r   NT   r(   r  u   첫 메시지r  r     u   두 번째 메시지r  r  r
   )rG   rx  rz  rj   rk   rl   rm   rn   ro   rp   rq   r   r2   r   r   r  )	rT   rF   rv  rx   r   ry   r   r   r  s	            r   'test_get_updates_updates_last_update_idz:TestTelegramPoller.test_get_updates_updates_last_update_id  s|   _""">$$))$))))$))))))v)))v)))$))))))))))K	 "%)-s_M
 "%)-s=ST	'
	# ,9E 	! 	! $$++$++++$++++++v+++v+++$++++++++++	! 	!s   #HHc                    t               }|j                  dd      }t               }dg d|j                  _        t        d|      5  |j                         }dd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}}|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                  |	      dz  }dd|iz  }t        t        j                  |            dx}x}
}	y# 1 sw Y   yxY w)ub   result가 빈 리스트이면 빈 리스트를 반환하고 last_update_id는 변하지 않는다.r}  r  ru  Tr  r  r
   NrZ   r   r  r   r   r   r   rw  rv  r  r   r   rG   rx  r   r2   r   r   r  rj   rk   rl   rm   rn   ro   rp   rq   rz  rT   rF   rv  r  r  r   rx   r   r   r   ry   r   s               r   ,test_get_updates_returns_empty_on_no_resultsz?TestTelegramPoller.test_get_updates_returns_empty_on_no_results  s0   _""">K	-1R&@	#,9E 	,))+H	, x2~x2xx2$$))$))))$))))))v)))v)))$))))))))))		, 	,s   	GGc                    t               }|j                  dd      }t               }dddddiid	gd
|j                  _        t        d|      5  |j                         }dd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}}|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                  |	      dz  }dd|iz  }t        t        j                  |            dx}x}
}	y# 1 sw Y   yxY w)uG   text 필드가 없는 메시지(사진, 스티커 등)는 스킵한다.r}  r  ru  Ti-  r  r(   r  r  r  r  r
   NrZ   r   r  r   r   r   rw  rv  r  r   r   r  r  s               r   ,test_get_updates_skips_messages_without_textz?TestTelegramPoller.test_get_updates_skips_messages_without_text  sO   _""">K	 "%s '
	# ,9E 	,))+H	, x2~x2xx2$$++$++++$++++++v+++v+++$++++++++++	, 	,s   GG#c                 r   t               }|j                  dd      }d|_        t               }dg d|j                  _        t        d|      5 }|j                          d	d	d	       j                  }t        |d
         dkD  r|d   j                  d      xs |d
   d   n|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)uA   get_updates()가 last_update_id + 1을 offset으로 전달한다.r}  r  ru  r*  Tr  r  r
   Nr   r  paramsoffsetr~  rZ   r   r   r   r`   )rG   rx  rz  r   r2   r   r   r  	call_argsr6   r   rj   rk   ro   rp   rq   )rT   rF   rv  r  mock_getcall_kwargsr  r   ry   r   r   r   s               r   &test_get_updates_passes_correct_offsetz9TestTelegramPoller.test_get_updates_passes_correct_offset  s   _"""> #K	-1R&@	#,9E 	! 	! ((AD[QR^ATWXAXKNx(=KN1,=^ijk^lmu^v 	 h&3&3&&&&3&&&&&&3&&&&&&&	! 	!s   D--D6N)r   r   r   r   r{  r  r  r  r  r  r  r  r  r   r   r   rq  rq  4  s2    -*/00,4*,4'r   rq  c                   ^    e Zd ZdZd Zd Zd Zd Zd Zd Z	d Z
d	 Zd
 Zd Zd Zd Zd Zy)TestDetectIntentu   detect_intent 함수 테스트.c                 >   t               }|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}}y)uG   '팀 모여' 키워드 → start_chat 인텐트 반환 (세션 없음).
   팀 모여Fsession_activeintent
start_chatrZ   r   r   r   r`   NrG   detect_intentrj   rk   ro   rp   rq   r+  s           r   test_start_keyword_team_gatherz/TestDetectIntent.test_start_keyword_team_gather  sp    _!!,u!Eh/</<////<//////<///////r   c                 >   t               }|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}}y)uI   '회의하자' 키워드 → start_chat 인텐트 반환 (세션 없음).   회의하자Fr  r  r  rZ   r   r   r   r`   Nr  r+  s           r   test_start_keyword_have_meetingz0TestDetectIntent.test_start_keyword_have_meeting  sp    _!!.!Gh/</<////<//////<///////r   c                    t               }|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}||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   }t        |t              }|sddt        j                         v st        j                  t              rt        j                  t              ndt        j                  |      dt        j                         v st        j                  t              rt        j                  t              ndt        j                  |      dz  }t        t        j                  |            dx}}y)u;   start_chat 인텐트에 topic과 personas 필드가 있다.r  Fr  ru  r   r   r   r   r   r   Nr  z5assert %(py5)s
{%(py5)s = %(py0)s(%(py2)s, %(py3)s)
}r   rC   )r]   r^   r   r   )rG   r  rj   rk   ro   rl   rm   rn   rp   rq   r   rC   )	rT   rF   r   r   r   r   r   rx   r   s	            r   -test_start_chat_result_has_topic_and_personasz>TestDetectIntent.test_start_chat_result_has_topic_and_personas  sP   _!!,u!E w&    w&   w      &   &       #zV####zV###z######V###V####### ,3z,d33333333z333z333,333333d333d3333333333r   c                    t               }d}t        d|      5 }|j                  dd      }ddd       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# 1 sw Y   xY w)uL   일반 메시지 '안녕' → Claude 호출 후 none 반환 (세션 없음).{"intent": "none"}r  r
   r  Fr  Nr  nonerZ   r   r   r   r`   	rG   r   r  assert_called_oncerj   rk   ro   rp   rq   
rT   rF   fake_responsemock_clauder   r   ry   r   r   r   s
             r   0test_normal_message_calls_claude_when_no_sessionzATestDetectIntent.test_normal_message_calls_claude_when_no_session  s    _,+-H 	FK%%hu%EF	F&&(h)6)6))))6))))))6)))))))	F 	F   CCc                 >   t               }|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}}y)uA   '잘래' 키워드 → end_chat 인텐트 반환 (세션 활성).   잘래Tr  r  end_chatrZ   r   r   r   r`   Nr  r+  s           r   test_end_keyword_sleepz'TestDetectIntent.test_end_keyword_sleep  p    _!!(4!@h-:-:----:------:-------r   c                 >   t               }|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}}y)uA   '빠이' 키워드 → end_chat 인텐트 반환 (세션 활성).   빠이Tr  r  r  rZ   r   r   r   r`   Nr  r+  s           r   test_end_keyword_byez%TestDetectIntent.test_end_keyword_bye  r  r   c                    t               }d}t        d|      5 }|j                  dd      }ddd       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# 1 sw Y   xY w)uF   일반 질문 → Claude 호출 → user_input 반환 (세션 활성).u<   {"intent": "user_input", "message": "오늘 일정 어때?"}r  r
   u   오늘 일정 어때?Tr  Nr  rj  rZ   r   r   r   r`   r  r  s
             r   5test_normal_question_calls_claude_when_session_activezFTestDetectIntent.test_normal_question_calls_claude_when_session_active  s    _V+-H 	TK%%&=d%SF	T&&(h/</<////<//////<///////	T 	Tr  c                    t               }t        dt        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}||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)uC   Claude 호출 실패 시 session_active=True → user_input 폴백.r     Claude 실패r  u   어떻게 생각해?Tr  Nr  rj  rZ   r   r   r   r`   rl  	rG   r   r2  r  rj   rk   ro   rp   rq   r+  s           r   (test_claude_fallback_when_session_activez9TestDetectIntent.test_claude_fallback_when_session_active  s    _+?9ST 	S%%&<T%RF	Sh/</<////<//////<///////i :$:: $::::: $:::: :::$::::::::	S 	Ss   D>>Ec                    t               }t        dt        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}}y# 1 sw Y   xY w)u>   Claude 호출 실패 시 session_active=False → none 폴백.r  r  r  r  Fr  Nr  r  rZ   r   r   r   r`   r  r+  s           r   *test_claude_fallback_when_session_inactivez;TestDetectIntent.test_claude_fallback_when_session_inactive"  s    _+?9ST 	F%%hu%EF	Fh)6)6))))6))))))6)))))))	F 	Fs   B>>Cc                    t               }t        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}}y# 1 sw Y   xY w)uX   Claude가 유효하지 않은 JSON 반환 시 session_active=True → user_input 폴백.r     유효하지 않은 응답r
      어떤 질문Tr  Nr  rj  rZ   r   r   r   r`   rG   r   r  rj   rk   ro   rp   rq   r+  s           r   0test_claude_returns_invalid_json_fallback_activezATestDetectIntent.test_claude_returns_invalid_json_fallback_active)  s    _+:VW 	L%%od%KF	Lh/</<////<//////<///////	L 	L   B55B>c                    t               }t        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}}y# 1 sw Y   xY w)uS   Claude가 유효하지 않은 JSON 반환 시 session_active=False → none 폴백.r  r  r
   r  Fr  Nr  r  rZ   r   r   r   r`   r  r+  s           r   2test_claude_returns_invalid_json_fallback_inactivezCTestDetectIntent.test_claude_returns_invalid_json_fallback_inactive0  s    _+:VW 	M%%oe%LF	Mh)6)6))))6))))))6)))))))	M 	Mr  c                    t               }t        dd      5  |j                  dd      }ddd       d   }d	}||k7  }|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)uO   세션 활성 중에는 시작 키워드가 end_chat을 유발하지 않는다.r  u1   {"intent": "user_input", "message": "팀 모여"}r
   r  Tr  Nr  r  r5  z%(py1)s != %(py4)sr   r   r`   r  r+  s           r   3test_start_keywords_not_matched_when_session_activezDTestDetectIntent.test_start_keywords_not_matched_when_session_active7  s    _+:mn 	I%%l4%HF	I h-:-:----:------:-------	I 	Ir  c                    t               }t        dd      5  |j                  dd      }ddd       d   }d	}||k7  }|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)uY   세션 비활성 중에는 종료 키워드가 즉각 end_chat을 반환하지 않는다.r  r  r
   r  Fr  Nr  r  r5  r  r   r   r`   r  r+  s           r   3test_end_keywords_not_matched_when_session_inactivezDTestDetectIntent.test_end_keywords_not_matched_when_session_inactive?  s    _+:NO 	F%%hu%EF	F h-:-:----:------:-------	F 	Fr  N)r   r   r   r   r  r  r  r  r  r  r  r  r  r  r  r  r  r   r   r   r  r    sF    )004*..0;*0*..r   r  c                   .    e Zd ZdZd Zd Zd Zd Zd Zy)TestIsActiveu1   GroupChatSession.is_active() 메서드 테스트.c                    |j                   } |       }d}||u }|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)	u9   초기 상태에서 is_active()는 False를 반환한다.Fr   zH%(py4)s
{%(py4)s = %(py2)s
{%(py2)s = %(py0)s.is_active
}()
} is %(py7)sr  r  r  r  N)		is_activerj   rk   rl   rm   rn   ro   rp   rq   rT   r  rx   ry   r  rz   r   r  s           r   'test_is_active_returns_false_by_defaultz4TestIsActive.test_is_active_returns_false_by_defaultQ  s    $$/$&/%/&%////&%//////{///{///$///&///%///////r   c                    d|_         |j                  } |       }d}||u }|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)	uB   active=False로 설정하면 is_active()가 False를 반환한다.Fr   r  r  r  r  r  N
r$   r  rj   rk   rl   rm   rn   ro   rp   rq   r  s           r   .test_is_active_returns_false_when_active_falsez;TestIsActive.test_is_active_returns_false_when_active_falseU  s    "$$/$&/%/&%////&%//////{///{///$///&///%///////r   c                    ddgd}t        j                  |d      5  t        d      5  t        dd      5  |j                  |       d	d	d	       d	d	d	       d	d	d	       |j                  } |       }d
}||u }|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# 1 sw Y   xY w# 1 sw Y   xY w)u7   start() 호출 후 is_active()가 True를 반환한다.	   테스트r   r  r  r  r     인사r
   NTr   r  r  r  r  r  )r   rh   rr  r  rj   rk   rl   rm   rn   ro   rp   rq   )	rT   r  r  rx   ry   r  rz   r   r  s	            r   'test_is_active_returns_true_after_startz4TestIsActive.test_is_active_returns_true_after_startZ  s   'hZ@\\+v. 	/01 /3(K /%%g.//	/ $$.$&.$.&$....&$......{...{...$...&...$......./ // /	/ 	/s9   EEE	EEEEE	EE)c           	         ddgd}|dz  }t        j                  |d      5  t        d      5  t        dd	      5  |j                  |       d
d
d
       d
d
d
       d
d
d
       |j                  } |       }d}||u }|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}}t        j                  |d      5  t        dt        |            5  t        d      5  t        dd	      5  |j                  d       d
d
d
       d
d
d
       d
d
d
       d
d
d
       |j                  } |       }d}||u }|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# 1 sw Y   `xY w# 1 sw Y   exY w# 1 sw Y   .xY w# 1 sw Y   3xY w# 1 sw Y   8xY w# 1 sw Y   =xY w)uK   start() 후 end() 호출하면 is_active()가 다시 False를 반환한다.r  r   r  session.jsonr  r  r  r  r
   NTr   r  r  r  r  r  r  r     작별r  F)r   rh   rr  r  rj   rk   rl   rm   rn   ro   rp   rq   ri   r   )rT   r  rr   r  r  rx   ry   r  rz   r   r  s              r   &test_is_active_returns_false_after_endz3TestIsActive.test_is_active_returns_false_after_endc  s+   'hZ@.0\\+v. 	/01 /3(K /%%g.//	/
 $$.$&.$.&$....&$......{...{...$...&...$.......\\+v. 	50#l2CD 589 57hO 5#4555	5 $$/$&/%/&%////&%//////{///{///$///&///%//////// // /	/ 	/5 55 55 5	5 	5s   KJ4J'J4K K56K(KK	"K*K(2K5'J1,J44J>	9KKKKK% K((K2	-K55K?c                    d|_         |j                  } |       }d}||u }|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}}d	|_         |j                  } |       }d	}||u }|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)
uF   is_active()가 self.active 속성과 일치하는 값을 반환한다.Tr   r  r  r  r  r  NFr  r  s           r   (test_is_active_reflects_active_attributez5TestIsActive.test_is_active_reflects_active_attributew  s    !$$.$&.$.&$....&$......{...{...$...&...$......."$$/$&/%/&%////&%//////{///{///$///&///%///////r   N)	r   r   r   r   r  r  r  r  r  r   r   r   r  r  N  s    ;00
/0(0r   r  c                   :    e Zd ZdZd Zd Zd Zd Zd Zd Z	d Z
y	)
TestRunOneTurnu4   GroupChatSession.run_one_turn() 메서드 테스트.c                     d|_         d|_        ddg|_        ddd|_        d|_        t        j
                         |_        d|_        g |_        y)	u6   테스트용 활성 세션 상태를 만드는 헬퍼.Tu   런원턴 테스트r   r   r   r  r   N)	r$   ru  r  r  r4  r$  r  r  r  )rT   r  s     r   _make_active_sessionz#TestRunOneTurn._make_active_session  sR    !1 ((3./1#= #% $(IIK!!" r   c                    d|_         t        d      5 }t        j                  |d      5 }|j                          ddd       ddd       j	                          j	                          y# 1 sw Y   2xY w# 1 sw Y   6xY w)u5   active=False이면 아무 동작도 하지 않는다.FrW  r`  N)r$   r   rh   run_one_turnrS   rT   r  r[  
mock_speaks       r   test_does_nothing_when_inactivez.TestRunOneTurn.test_does_nothing_when_inactive  su    "34 	+k73 +z((*+	+ 	%%'$$&	+ +	+ 	+s!   A8A,A8,A5	1A88Bc           	         d|_         d|_        dg|_        ddi|_        d|_        t        j
                         dz
  |_        d|_        g |_        |dz  }t        j                  |d      5  t        d	t        |            5  t        d
      5  t        dd      5  |j                          ddd       ddd       ddd       ddd       |j                   }d}||u }|st        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  }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# 1 sw Y   xY w)uB   last_activity가 SESSION_TIMEOUT 초과 시 end()를 호출한다.Tu   타임아웃 테스트r   r   r   i  r  r  r  r  r  r  r
   NFr   r  r  r  r   r   )r$   ru  r  r  r4  r$  r  r  r  r   rh   ri   r  rj   rk   rl   rm   rn   ro   rp   rq   r  s	            r   test_calls_end_on_timeoutz(TestRunOneTurn.test_calls_end_on_timeout  sc   !4 (z$,a= #% $(IIK#$5!!" .0\\+v. 	30#l2CD 389 37hO 3#002333	3 !!*U*!U****!U******{***{***!***U*******3 33 33 3	3 	3sT   ,F9F-F!F	-F!5F-=F9FF!!F*&F--F6	2F99Gc                 *   | j                  |       d|_        t        d      5 }t        j                  |d      5 }|j	                          ddd       ddd       j                          j                          y# 1 sw Y   2xY w# 1 sw Y   6xY w)uE   auto_turns >= MAX_AUTO_TURNS이면 speak()를 호출하지 않는다.r)  rW  r`  N)r  r  r   rh   r  rS   r  s       r   /test_does_not_speak_when_max_auto_turns_reachedz>TestRunOneTurn.test_does_not_speak_when_max_auto_turns_reached  s    !!+.!"34 	+k73 +z((*+	+ 	%%'$$&	+ +	+ 	+s"   B	A=B	=B	B		Bc                    | j                  |       t        dd      5  t        j                  |dd      5 }|j                          ddd       ddd       j	                  d       y# 1 sw Y   #xY w# 1 sw Y   'xY w)u>   정상 상태에서 run_one_turn()은 speak()를 호출한다.rW  r   r
   r`  TN)r  r   rh   r  assert_called_once_withrT   r  r  s      r   test_calls_speak_on_normal_turnz.TestRunOneTurn.test_calls_speak_on_normal_turn  ss    !!+.3(K 	+k7F +*((*+	+ 	**84+ +	+ 	+s"   A7A+	A7+A4	0A77B c                    | j                  |       t        dt        d            5  t        j                  |dd      5 }|j	                          ddd       ddd       j                  d       y# 1 sw Y   #xY w# 1 sw Y   'xY w)	u]   select_next_speaker 오류 시 personas[0]를 폴백으로 사용해 speak()를 호출한다.rW  u   선택 오류r  r`  Tr
   Nr   )r  r   r2  rh   r  r  r  s      r   %test_fallback_speaker_on_select_errorz4TestRunOneTurn.test_fallback_speaker_on_select_error  sx    !!+.3?A[\ 	+k7F +*((*+	+
 	**84	+ +	+ 	+s#   B A4B 4A=	9B  B	c                    | j                  |       |dz  }t        dd      5  t        j                  |dt        d            5  t        j                  |d      5  t        d	t	        |            5  t        d
      5  t        dd      5  |j                          ddd       ddd       ddd       ddd       ddd       ddd       |j                  }d}||u }|st        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  }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# 1 sw Y   xY w# 1 sw Y   xY w# 1 sw Y   xY w)u5   speak() 중 예외 발생 시 세션을 종료한다.r  rW  r   r
   r`  u   발화 오류r  r  r  r  r  r  NFr   r  r  r  r   r   )r  r   rh   r2  ri   r  r$   rj   rk   rl   rm   rn   ro   rp   rq   r  s	            r   $test_ends_session_on_speak_exceptionz3TestRunOneTurn.test_ends_session_on_speak_exception  sj   !!+..03(K 	;k7	/@Z[ ;\\+v6 ;8#l:KL ;"#@A ;!&'?h!W ; + 8 8 :;;;;;	; !!*U*!U****!U******{***{***!***U*******; ;; ;; ;; ;; ;	; 	;s   "GGF;3F.	?F"FF"&F.	.F;6G>GFF""F+'F.	.F83F;;G GG	GGN)r   r   r   r   r  r  r  r  r   r  r  r   r   r   r  r    s(    >	!	'+*
'5	5+r   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
)TestCheckTriggerFileu$   check_trigger_file 함수 테스트.c                 0   t               }t               }t        dd      5  |j                  |d       ddd       |j                  j                          |j                  j                          |j                  j                          y# 1 sw Y   XxY w)u@   트리거 파일이 없으면 아무 동작도 하지 않는다.rV  Nr
   r  )rG   r   r   check_trigger_filerr  rS   r  r   )rT   rF   sessions      r   !test_does_nothing_when_no_triggerz6TestCheckTriggerFile.test_does_nothing_when_no_trigger  st    _+,4@ 	9!!'<8	9 	'')002%%'	9 	9s   BBc                     t               }t               }d|j                  _        dddgd}t	        d|      5  |j                  |d       d	d	d	       |j                  j                  |       y	# 1 sw Y   %xY w)
u2   action=start 트리거 → session.start() 호출.Frr  rs  r   rt  ru  r  rV  r
   r  N)rG   r   r  r   r   r  rr  r  rT   rF   r	  r  s       r   %test_action_start_calls_session_startz:TestCheckTriggerFile.test_action_start_calls_session_start  sp    _+).&$XJW,7C 	9!!'<8	9 	--g6	9 	9s   A11A:c                     t               }t               }d|j                  _        ddd}t	        d|      5  |j                  |d       ddd       |j                  j                  d       y# 1 sw Y   %xY w)	uF   action=user_input + 활성 세션 → session.add_user_input() 호출.Trj  u   좋은 의견이에요rk  rV  r
   r  N)rG   r   r  r   r   r  r  r  r  s       r   *test_action_user_input_with_active_sessionz?TestCheckTriggerFile.test_action_user_input_with_active_session  sp    _+)-&)6NO,7C 	9!!'<8	9 	667OP	9 	9   A//A8c                     t               }t               }d|j                  _        ddd}t	        d|      5  |j                  |d       ddd       |j                  j                  d       y# 1 sw Y   %xY w)	u4   action=end + 활성 세션 → session.end() 호출.Tr   r  rR  rV  r
   r  N)rG   r   r  r   r   r  r   r  r  s       r   #test_action_end_with_active_sessionz8TestCheckTriggerFile.test_action_end_with_active_session  sl    _+)-&"k:,7C 	9!!'<8	9 	++K8	9 	9r  c                     t               }t               }d|j                  _        ddd}t	        d|      5  |j                  |d       ddd       |j                  j                          y# 1 sw Y   $xY w)	uD   action=user_input + 비활성 세션 → add_user_input() 미호출.Frj  u   무시될 메시지rk  rV  r
   r  N)rG   r   r  r   r   r  r  rS   r  s       r   7test_action_user_input_with_inactive_session_is_ignoredzLTestCheckTriggerFile.test_action_user_input_with_inactive_session_is_ignored  sm    _+).&)6KL,7C 	9!!'<8	9 	002	9 	9   A..A7c                     t               }t               }d|j                  _        ddd}t	        d|      5  |j                  |d       ddd       |j                  j                          y# 1 sw Y   $xY w)	u2   action=end + 비활성 세션 → end() 미호출.Fr   r  rR  rV  r
   r  N)rG   r   r  r   r   r  r   rS   r  s       r   0test_action_end_with_inactive_session_is_ignoredzETestCheckTriggerFile.test_action_end_with_inactive_session_is_ignored+  sj    _+).&"k:,7C 	9!!'<8	9 	%%'	9 	9r  c                 0   t               }t               }d|j                  _        dddgd}t	        d|      5  |j                  |d       d	d	d	       |j                  j                  d
       |j                  j                  |       y	# 1 sw Y   @xY w)uO   action=start + 이미 활성 세션 → 기존 세션 end() 후 start() 호출.Trr  u
   새 회의r   r  rV  r
   r  Nrestart)	rG   r   r  r   r   r  r   r  rr  r  s       r   -test_action_start_ends_existing_session_firstzBTestCheckTriggerFile.test_action_start_ends_existing_session_first8  s    _+)-&$|(T,7C 	9!!'<8	9 	++I6--g6		9 	9s   BBc                 V   t               }t               }d|j                  _        t	        d      |j
                  _        dddd}t        d|      5  t        d	      5 }|j                  |d
       ddd       ddd       j                          y# 1 sw Y   "xY w# 1 sw Y   &xY w)u;   session.start() 오류 시 Telegram 알림을 전송한다.Fu   시작 오류rr  u   오류 테스트r  )rt  ru  r  rV  r
   group_chat.send_telegramr  N)
rG   r   r  r   r2  rr  r  r   r  r  )rT   rF   r	  r  mock_send_tgs        r   3test_action_start_error_sends_telegram_notificationzHTestCheckTriggerFile.test_action_start_error_sends_telegram_notificationF  s    _+).&$-o$>!$/AeT,7C 	=12 =l%%g|<=	= 	'')= =	= 	=s$   BB2BB	BB(N)r   r   r   r   r
  r  r  r  r  r  r  r  r   r   r   r  r    s.    .
(7Q93(7*r   r  c                   R    e Zd ZdZd Zd Zd Zd Zd Zd Z	d Z
d	 Zd
 Zd Zd Zy)TestNewConstantsu)   새로 추가된 상수 검증 테스트.c                    t               }d}t        ||      }|sd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&   START_KEYWORDS 상수가 존재한다.START_KEYWORDS5assert %(py5)s
{%(py5)s = %(py0)s(%(py1)s, %(py3)s)
}r   rF   r   N	rG   r   rl   rm   rj   rn   ro   rp   rq   rT   rF   r   r   r   s        r   test_start_keywords_existsz+TestNewConstants.test_start_keywords_exists^  s    _+,wr+,,,,,,,,w,,,w,,,,,,r,,,r,,,+,,,,,,,,,,r   c                    t               }|j                  }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t        j                  |      dt	        j
                         v st        j                  t              rt        j                  t              ndt        j                  |      dz  }t        t        j                  |            dx}}y)u)   START_KEYWORDS가 리스트 타입이다.zYassert %(py6)s
{%(py6)s = %(py0)s(%(py3)s
{%(py3)s = %(py1)s.START_KEYWORDS
}, %(py4)s)
}r   rF   rC   rb  N)rG   r#  r   rC   rl   rm   rj   rn   ro   rp   rq   rc  s        r   test_start_keywords_is_listz,TestNewConstants.test_start_keywords_is_listc  s    _++2z+T22222222z222z222222"222"222+222222T222T2222222222r   c                 4   t               }d}|j                  }||v }|st        j                  d|fd||f      t        j                  |      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      dz  }dd|iz  }t        t        j                  |            dx}x}}d	}|j                  }||v }|st        j                  d|fd||f      t        j                  |      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      dz  }dd|iz  }t        t        j                  |            dx}x}}y)
u?   START_KEYWORDS에 '팀 모여', '회의하자'가 포함된다.r  r   )z6%(py1)s in %(py5)s
{%(py5)s = %(py3)s.START_KEYWORDS
}rF   r   r   r   Nr  )
rG   r#  rj   rk   ro   rl   rm   rn   rp   rq   rT   rF   r   r   r   r   r   s          r   +test_start_keywords_contains_expected_itemsz<TestNewConstants.test_start_keywords_contains_expected_itemsh  s    _0r000|00000|0000|000000r000r000000000002!2!22~!22222~!2222~222222222222!22222222r   c                    t               }d}t        ||      }|sd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$   END_KEYWORDS 상수가 존재한다.END_KEYWORDSr$  r   rF   r   Nr%  r&  s        r   test_end_keywords_existsz)TestNewConstants.test_end_keywords_existsn  s    _)*wr>********w***w******r***r***>**********r   c                    t               }|j                  }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t        j                  |      dt	        j
                         v st        j                  t              rt        j                  t              ndt        j                  |      dz  }t        t        j                  |            dx}}y)u'   END_KEYWORDS가 리스트 타입이다.zWassert %(py6)s
{%(py6)s = %(py0)s(%(py3)s
{%(py3)s = %(py1)s.END_KEYWORDS
}, %(py4)s)
}r   rF   rC   rb  N)rG   r.  r   rC   rl   rm   rj   rn   ro   rp   rq   rc  s        r   test_end_keywords_is_listz*TestNewConstants.test_end_keywords_is_lists  s    _//0z/400000000z000z000000"000"000/000000400040000000000r   c                 4   t               }d}|j                  }||v }|st        j                  d|fd||f      t        j                  |      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      dz  }dd|iz  }t        t        j                  |            dx}x}}d	}|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)
u3   END_KEYWORDS에 '잘래', '빠이'가 포함된다.r  r   )z4%(py1)s in %(py5)s
{%(py5)s = %(py3)s.END_KEYWORDS
}rF   r   r   r   Nr  )
rG   r.  rj   rk   ro   rl   rm   rn   rp   rq   r+  s          r   )test_end_keywords_contains_expected_itemsz:TestNewConstants.test_end_keywords_contains_expected_itemsx  s    _*2??*x?****x?***x******2***2***?********2??*x?****x?***x******2***2***?*******r   c                    t               }d}t        ||      }|sd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'   ALL_PERSONA_IDS 상수가 존재한다.ALL_PERSONA_IDSr$  r   rF   r   Nr%  r&  s        r   test_all_persona_ids_existsz,TestNewConstants.test_all_persona_ids_exists~  s    _,-wr,--------w---w------r---r---,----------r   c                    t               }|j                  }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t        j                  |      dt	        j
                         v st        j                  t              rt        j                  t              ndt        j                  |      dz  }t        t        j                  |            dx}}y)u*   ALL_PERSONA_IDS가 리스트 타입이다.zZassert %(py6)s
{%(py6)s = %(py0)s(%(py3)s
{%(py3)s = %(py1)s.ALL_PERSONA_IDS
}, %(py4)s)
}r   rF   rC   rb  N)rG   r5  r   rC   rl   rm   rj   rn   ro   rp   rq   rc  s        r   test_all_persona_ids_is_listz-TestNewConstants.test_all_persona_ids_is_list  s    _,,3z,d33333333z333z333333"333"333,333333d333d3333333333r   c                 ~   t               }|j                  }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                  |      t        j                  |      dz  }dd|iz  }t        t        j                  |            d	x}x}x}}y	)
u-   ALL_PERSONA_IDS에 정확히 19명이 있다.   rZ   )zU%(py5)s
{%(py5)s = %(py0)s(%(py3)s
{%(py3)s = %(py1)s.ALL_PERSONA_IDS
})
} == %(py8)sr6   rF   r  r  r  N)rG   r5  r6   rj   rk   rl   rm   rn   ro   rp   rq   )rT   rF   r   r   r{   r  r   r  s           r   #test_all_persona_ids_has_19_membersz4TestNewConstants.test_all_persona_ids_has_19_members  s    _%%,s%&,",&",,,,&",,,,,,s,,,s,,,,,,2,,,2,,,%,,,&,,,",,,,,,,r   c                    t               }h d}|j                  }t        |      }||k(  }|s7t        j                  d|fd||f      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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
)u7   ALL_PERSONA_IDS에 주요 페르소나가 포함된다.>   r  r   rR  r   r  r   r   rS  rT  rU  r  rV  rW  r   r  r   r   r   rX  rZ   )zU%(py0)s == %(py7)s
{%(py7)s = %(py2)s(%(py5)s
{%(py5)s = %(py3)s.ALL_PERSONA_IDS
})
}expectedr4   rF   )r]   r^   r   r   r   r  r  N)rG   r5  r4   rj   rk   rl   rm   rn   ro   rp   rq   )rT   rF   r=  r   r  rx   r   r  s           r   .test_all_persona_ids_contains_expected_membersz?TestNewConstants.test_all_persona_ids_contains_expected_members  s    _
*  "1123122x22222x2222222x222x22222232223222222r222r222122222222222r   c                    t               }|j                  }t        |      }|j                  }t        |      }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                  |      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}x}x}}y	)
u$   ALL_PERSONA_IDS에 중복이 없다.rZ   )z%(py5)s
{%(py5)s = %(py0)s(%(py3)s
{%(py3)s = %(py1)s.ALL_PERSONA_IDS
})
} == %(py15)s
{%(py15)s = %(py7)s(%(py13)s
{%(py13)s = %(py8)s(%(py11)s
{%(py11)s = %(py9)s.ALL_PERSONA_IDS
})
})
}r6   rF   r4   )
r]   r   r   r   r   ra   r  rb   rd   py15zassert %(py17)spy17N)rG   r5  r6   r4   rj   rk   rl   rm   rn   ro   rp   rq   )
rT   rF   r   r   r|   @py_assert12@py_assert14r  @py_format16@py_format18s
             r   "test_all_persona_ids_no_duplicatesz3TestNewConstants.test_all_persona_ids_no_duplicates  sH   _%%Fs%&F"2D2DFc2D.EF#.E*FF&*FFFFF&*FFFFFFFsFFFsFFFFFF2FFF2FFF%FFF&FFFFFF#FFF#FFFFFFcFFFcFFFFFF"FFF"FFF2DFFF.EFFF*FFFFFFFFFr   N)r   r   r   r   r'  r)  r,  r/  r1  r3  r6  r8  r;  r>  rF  r   r   r   r!  r!  [  s=    3-
3
3+
1
+.
4
-
34Gr   r!  c                   X    e Zd ZdZd Zd Zd Zd Zd Zd Z	d Z
d	 Zd
 Zd Zd Zd Zy)TestDetectIntentControluG   세션 활성 중 제어 명령 감지 테스트 (session_active=True).c                    t               }t        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}||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)u9   '10명만 얘기해' → control/limit_personas/count=10.r  u   10명만 얘기해Tr  Nr  controlrZ   r   r   r   r`   typelimit_personascount
   r  r+  s           r      test_limit_personas_10명만u4   TestDetectIntentControl.test_limit_personas_10명만  s3   _+, 	Q%%&:4%PF	Qh,9,9,,,,9,,,,,,9,,,,,,,f~1!11~!11111~!1111~111!11111111g$"$"$$$$"$$$$$$"$$$$$$$		Q 	Q   F33F=c                    t               }t        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}||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)u3   '5명까지만' → control/limit_personas/count=5.r  u   5명까지만Tr  Nr  rJ  rZ   r   r   r   r`   rK  rL  rM  r  r  r+  s           r   !   test_limit_personas_5명까지만u9   TestDetectIntentControl.test_limit_personas_5명까지만  s2   _+, 	L%%od%KF	Lh,9,9,,,,9,,,,,,9,,,,,,,f~1!11~!11111~!1111~111!11111111g#!#!####!######!#######		L 	LrP  c                    t               }t        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}||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)u:   '로키 빠져' → control/remove_persona/persona='loki'.r  u   로키 빠져Tr  Nr  rJ  rZ   r   r   r   r`   rK  remove_personar  r   r  r+  s           r      test_remove_persona_로키u2   TestDetectIntentControl.test_remove_persona_로키  s6   _+, 	L%%od%KF	Lh,9,9,,,,9,,,,,,9,,,,,,,f~1!11~!11111~!1111~111!11111111i *F* F**** F*** ***F*******		L 	LrP  c                    t               }t        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}||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)u?   '아테나 나가' → control/remove_persona/persona='athena'.r  u   아테나 나가Tr  Nr  rJ  rZ   r   r   r   r`   rK  rT  r  r   r  r+  s           r      test_remove_persona_나가u2   TestDetectIntentControl.test_remove_persona_나가  s7   _+, 	O%%&8%NF	Oh,9,9,,,,9,,,,,,9,,,,,,,f~1!11~!11111~!1111~111!11111111i ,H, H,,,, H,,, ,,,H,,,,,,,		O 	OrP  c                    t               }t        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}||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)u;   '비너스 불러' → control/add_persona/persona='venus'.r  u   비너스 불러Tr  Nr  rJ  rZ   r   r   r   r`   rK  add_personar  r   r  r+  s           r      test_add_persona_불러u/   TestDetectIntentControl.test_add_persona_불러  s3   _+, 	O%%&8%NF	Oh,9,9,,,,9,,,,,,9,,,,,,,f~..~....~...~..........i +G+ G++++ G+++ +++G+++++++		O 	OrP  c                    t               }t        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}||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)u=   '로키 합류시켜' → control/add_persona/persona='loki'.r  u   로키 합류시켜Tr  Nr  rJ  rZ   r   r   r   r`   rK  rY  r  r   r  r+  s           r      test_add_persona_합류u/   TestDetectIntentControl.test_add_persona_합류  s3   _+, 	R%%&;D%QF	Rh,9,9,,,,9,,,,,,9,,,,,,,f~..~....~...~..........i *F* F**** F*** ***F*******		R 	RrP  c                 X   t               }t        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}||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        |      }h d}	||	k(  }
|
st        j                  d|
fd||	f      dt        j                         v st        j                  t              rt        j
                  t              ndt        j
                  |      t        j
                  |      t        j
                  |	      dz  }dd|iz  }t        t        j                  |            dx}x}x}
}	y# 1 sw Y   xY w)uU   '백엔드만 남아' → control/filter_by_role/personas=['vulcan','thor','anubis'].r  u   백엔드만 남아Tr  Nr  rJ  rZ   r   r   r   r`   rK  filter_by_roler     r   r  r   r  r4   r  r  r  rG   r   r  rj   rk   ro   rp   rq   r4   rl   rm   rn   rT   rF   r   r   ry   r   r   r   rx   r  rz   r   r  s                r      test_filter_by_role_백엔드u5   TestDetectIntentControl.test_filter_by_role_백엔드  sq   _+, 	R%%&;D%QF	Rh,9,9,,,,9,,,,,,9,,,,,,,f~1!11~!11111~!1111~111!11111111*%Fs%&F*FF&*FFFFF&*F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		R 	R   HH)c                 X   t               }t        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}||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        |      }h d}	||	k(  }
|
st        j                  d|
fd||	f      dt        j                         v st        j                  t              rt        j
                  t              ndt        j
                  |      t        j
                  |      t        j
                  |	      dz  }dd|iz  }t        t        j                  |            dx}x}x}
}	y# 1 sw Y   xY w)u%   '1팀만' → control/filter_by_team.r  u   1팀만Tr  Nr  rJ  rZ   r   r   r   r`   rK  filter_by_teamr     r   rS  r   r   r   r  r4   r  r  r  r`  ra  s                r      test_filter_by_team_1팀u0   TestDetectIntentControl.test_filter_by_team_1팀  sp   _+, 	F%%i%EF	Fh,9,9,,,,9,,,,,,9,,,,,,,f~1!11~!11111~!1111~111!11111111*%Ys%&Y*YY&*YYYYY&*YYYYYYYsYYYsYYY%YYY&YYY*YYYYYYYY		F 	Frc  c                    t               }t        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}||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)u@   '3턴까지만 자동으로' → control/set_auto_turns/count=3.r  u   3턴까지만 자동으로Tr  Nr  rJ  rZ   r   r   r   r`   rK  set_auto_turnsrM  r  r  r+  s           r      test_set_auto_turns_3턴u0   TestDetectIntentControl.test_set_auto_turns_3턴  s4   _+, 	Y%%&BSW%XF	Yh,9,9,,,,9,,,,,,9,,,,,,,f~1!11~!11111~!1111~111!11111111g#!#!####!######!#######		Y 	YrP  c                    t               }t        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}||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)u7   '계속 얘기해' → control/set_auto_turns/count=99.r  u   계속 얘기해Tr  Nr  rJ  rZ   r   r   r   r`   rK  ri  rM  c   r  r+  s           r      test_set_auto_turns_계속u2   TestDetectIntentControl.test_set_auto_turns_계속  s3   _+, 	O%%&8%NF	Oh,9,9,,,,9,,,,,,9,,,,,,,f~1!11~!11111~!1111~111!11111111g$"$"$$$$"$$$$$$"$$$$$$$		O 	OrP  c                 ~   t               }t        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}}y# 1 sw Y   xY w)u*   '끝' → end_chat (기존 기능 유지).r  u   끝Tr  Nr  r  rZ   r   r   r   r`   r  r+  s           r   test_end_chat_still_worksz1TestDetectIntentControl.test_end_chat_still_works  s    _+, 	B%%eD%AF	Bh-:-:----:------:-------	B 	Bs   B33B<c                    t               }d}t        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}}y# 1 sw Y   xY w)uW   일반 메시지는 제어 명령이 아닌 경우 Claude 호출 후 user_input 반환.uH   {"intent": "user_input", "message": "오늘 어떻게 진행할까요?"}r  r
   u!   오늘 어떻게 진행할까요?Tr  Nr  rj  rZ   r   r   r   r`   r  )	rT   rF   r  r   r   ry   r   r   r   s	            r   test_normal_user_inputz.TestDetectIntentControl.test_normal_user_input  s    _b+-H 	`%%&IZ^%_F	`h/</<////<//////<///////	` 	`s   B77C N)r   r   r   r   rO  rR  rU  rW  rZ  r\  rb  rg  rj  rm  ro  rq  r   r   r   rH  rH    sC    Q%$+-,+GZ$%.0r   rH  c                   4    e Zd ZdZd Zd Zd Zd Zd Zd Z	y)	TestDetectIntentStartPersonasuD   시작 시 자연어 인원 지정 테스트 (session_active=False).c           
         t               }t        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   }t        |      }|j                  }	t        |	      }
||
k(  }|sgt        j                  d|fd||
f      dt        j                         v st        j                  t              rt        j
                  t              ndt        j
                  |      t        j
                  |      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}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}}y# 1 sw Y   xY w)u<   '전원 집합' → personas 19명 (ALL_PERSONA_IDS 전체).r  u   전원 집합Fr  Nr  r  rZ   r   r   r   r`   r  )zu%(py4)s
{%(py4)s = %(py0)s(%(py2)s)
} == %(py11)s
{%(py11)s = %(py6)s(%(py9)s
{%(py9)s = %(py7)s.ALL_PERSONA_IDS
})
}r4   rF   )r]   r^   r_   r`   r   r  rb   rc   rd   r:  r  r6   r  r  r  )rG   r   r  rj   rk   ro   rp   rq   r4   r5  rl   rm   rn   r6   )rT   rF   r   r   ry   r   r   r   rx   @py_assert8r|   rz   r~   r   r  r   r  s                    r      test_전원_집합u0   TestDetectIntentStartPersonas.test_전원_집합'  s    _+, 	M%%oe%LF	Mh/</<////<//////<///////*%As%&Ab.@.@A#.@*A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AbAAAbAAA.@AAA*AAAAAAAA*%,s%&,",&",,,,&",,,,,,s,,,s,,,%,,,&,,,",,,,,,,		M 	Ms   L==Mc                 X   t               }t        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   }t        |      }h d}	||	k(  }
|
st        j                  d|
fd||	f      dt        j                         v st        j                  t              rt        j
                  t              ndt        j
                  |      t        j
                  |      t        j
                  |	      dz  }dd|iz  }t        t        j                  |            dx}x}x}
}	y# 1 sw Y   wxY w)uX   '백엔드만 소집' → personas=['vulcan','thor','anubis'] (소집 키워드 포함).r  u   백엔드만 소집Fr  Nr  r  rZ   r   r   r   r`   r  r_  r  r4   r  r  r  r`  ra  s                r      test_백엔드만_모여u6   TestDetectIntentStartPersonas.test_백엔드만_모여0  s   _+, 	S%%&;E%RF	Sh/</<////<//////<///////*%Fs%&F*FF&*FFFFF&*F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	S 	S   FF)c                 X   t               }t        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   }t        |      }h d}	||	k(  }
|
st        j                  d|
fd||	f      dt        j                         v st        j                  t              rt        j
                  t              ndt        j
                  |      t        j
                  |      t        j
                  |	      dz  }dd|iz  }t        t        j                  |            dx}x}x}
}	y# 1 sw Y   wxY w)uK   '1팀 모여봐' → personas=개발1팀 5명 (모여봐 키워드 포함).r  u   1팀 모여봐Fr  Nr  r  rZ   r   r   r   r`   r  rf  r  r4   r  r  r  r`  ra  s                r      test_1팀_모여u.   TestDetectIntentStartPersonas.test_1팀_모여8  s   _+, 	N%%&6u%MF	Nh/</<////<//////<///////*%Ys%&Y*YY&*YYYYY&*YYYYYYYsYYYsYYY%YYY&YYY*YYYYYYYY	N 	Nry  c                 X   t               }t        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   }t        |      }h d}	||	k(  }
|
st        j                  d|
fd||	f      dt        j                         v st        j                  t              rt        j
                  t              ndt        j
                  |      t        j
                  |      t        j
                  |	      dz  }dd|iz  }t        t        j                  |            dx}x}x}
}	y# 1 sw Y   wxY w)uN   '팀장 소집' → personas=['hermes','odin','ra'] (소집 키워드 포함).r  u   팀장 소집Fr  Nr  r  rZ   r   r   r   r`   r  >   r  r   r   r  r4   r  r  r  r`  ra  s                r      test_팀장들_모여u3   TestDetectIntentStartPersonas.test_팀장들_모여@  s   _+, 	M%%oe%LF	Mh/</<////<//////<///////*%Bs%&B*BB&*BBBBB&*BBBBBBBsBBBsBBB%BBB&BBB*BBBBBBBB	M 	Mry  c                 T   t               }t        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   }t        |      }d}	||	k(  }
|
st        j                  d|
fd||	f      dt        j                         v st        j                  t              rt        j
                  t              ndt        j
                  |      t        j
                  |      t        j
                  |	      dz  }dd|iz  }t        t        j                  |            dx}x}x}
}	y# 1 sw Y   uxY w)ua   '전원 집합 5명만' → personas 5명으로 제한 (전원 집합 키워드 + 인원 제한).r  u   전원 집합 5명만Fr  Nr  r  rZ   r   r   r   r`   r  r  r  r6   r  r  r  )rG   r   r  rj   rk   ro   rp   rq   r6   rl   rm   rn   ra  s                r      test_5명만_모여u1   TestDetectIntentStartPersonas.test_5명만_모여H  s   _+, 	U%%&=e%TF	Uh/</<////<//////<///////*%+s%&+!+&!++++&!++++++s+++s+++%+++&+++!+++++++	U 	Us   FF'c                    t               }t        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   }g 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)u8   '팀 모여' → 기본 3명 ['hermes','athena','thor'].r  r  Fr  Nr  r  rZ   r   r   r   r`   r  r  r  r+  s           r      test_기본_소집u0   TestDetectIntentStartPersonas.test_기본_소집P  s    _+, 	J%%l5%IF	Jh/</<////<//////<///////j!A%AA!%AAAAA!%AAAA!AAA%AAAAAAAA	J 	Js   D55D?N)
r   r   r   r   rv  rx  r{  r}  r  r  r   r   r   rs  rs  $  s'    N-GZC,Br   rs  r   r(  r  r7  r   r   r   u   불칸u   백엔드 개발자r   u	   이리스u   프론트엔드 개발자r   r0  u   UX/UI 설계자rS  u   아르고스u   QA 엔지니어r   rH  r  rI  r   r1  rT  u   프레이야rV  u	   미미르u   UX 설계자rX  u	   헤임달r  u   라u
   개발3팀u   개발3팀장r  u   아누비스rR  u	   이시스rW  u	   소베크rU  u	   호루스r   r)  u	   레드팀u   레드팀 리더r  r*  r  u   QC 센터장u	   야누스r  u   DevOps 센터장u	   비너스u   디자인 센터u   디자인 센터장)r  r   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)TestHandleControlu#   handle_control 메서드 테스트.Nc                 @   t               }|j                  dt              }d|_        d|_        |t        |      |_        n"t        t        j                               |_        |t        |      |_	        |S |j                  D ci c]  }|d c}|_	        |S c c}w )uB   테스트용 GroupChatSession 인스턴스를 생성하는 헬퍼.rs  r  Tr  r   )
rG   r  _ALL_PERSONAS_DATAr$   r  rC   r  rD   rO   r  )rT   r  r  rF   r	  r  s         r   _make_sessionzTestHandleControl._make_session  s    _%%LHZ%[#H~G#$6$;$;$=>G##'#5G   3:2B2B#CQAqD#CG  $Ds   
Bc                 N	   | j                         }|j                  }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                  |      t        j                  |      dz  }dd|iz  }t        t        j                  |            d	x}x}x}}t        d
      5 }|j                  ddd       d	d	d	       |j                  }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                  |      t        j                  |      dz  }dd|iz  }t        t        j                  |            d	x}x}x}}|j                  }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                  |      t        j                  |      dz  }dd|iz  }t        t        j                  |            d	x}x}x}}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}
}y	# 1 sw Y   8xY w)uH   19명에서 5명으로 축소하고 send_system_message를 호출한다.r:  rZ   zN%(py5)s
{%(py5)s = %(py0)s(%(py3)s
{%(py3)s = %(py1)s.personas
})
} == %(py8)sr6   r	  r  r  r  Nr  rL  r  rK  rM  )zR%(py5)s
{%(py5)s = %(py0)s(%(py3)s
{%(py3)s = %(py1)s.speak_counts
})
} == %(py8)sr   r  u   ⚙️r   r   	sent_textr   r   r   )r  r  r6   rj   rk   rl   rm   rn   ro   rp   rq   r   handle_controlr  r  r  )rT   r	  r   r   r{   r  r   r  r  r  r   r   r   s                r   test_limit_personasz%TestHandleControl.test_limit_personas	  s   $$&##*s#$**$****$******s***s******7***7***#***$**********-. 	K,"",<q#IJ	K ##)s#$))$))))$))))))s)))s))))))7)))7)))#)))$))))))))))''-s'(-A-(A----(A------s---s------7---7---'---(---A-------'') **1-a0	$x9$$$$x9$$$x$$$$$$9$$$9$$$$$$$	K 	Ks   RR$c                    | j                  g d      }t        d      5 }|j                  ddd       ddd       |j                  }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                  |      t        j                  |      dz  }dd|iz  }t        t        j                  |            dx}x}x}}j                          y# 1 sw Y   NxY w)u>   count=0이면 무시되고 personas가 변경되지 않는다.r  )r  r  rL  r   r  Nr  rZ   r  r6   r	  r  r  r  )r  r   r  r  r6   rj   rk   rl   rm   rn   ro   rp   rq   rS   )	rT   r	  r  r   r   r{   r  r   r  s	            r    test_limit_personas_zero_ignoredz2TestHandleControl.test_limit_personas_zero_ignored	  s   $$.J$K-. 	K,"",<q#IJ	K ##)s#$))$))))$))))))s)))s))))))7)))7)))#)))$))))))))))&&(		K 	Ks   FFc                 "   | j                  g ddddd      }t        d      5  |j                  ddd	       d
d
d
       d}|j                  }||v}|st	        j
                  d|fd||f      t	        j                  |      dt        j                         v st	        j                  |      rt	        j                  |      ndt	        j                  |      dz  }dd|iz  }t        t	        j                  |            d
x}x}}d}|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}}|j                  }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                  |      t	        j                  |      dz  }	dd|	iz  }
t        t	        j                  |
            d
x}x}x}}y
# 1 sw Y   xY w)uD   특정 인원을 퇴장시키고 speak_counts에서도 제거한다.r  r  r  r  r  r  r  rT  r   rK  r  Nr   )z4%(py1)s not in %(py5)s
{%(py5)s = %(py3)s.personas
}r	  r   r   r   z8%(py1)s not in %(py5)s
{%(py5)s = %(py3)s.speak_counts
}rZ   r  r6   r  r  r  r  r   r  r  rj   rk   ro   rl   rm   rn   rp   rq   r  r6   )rT   r	  r   r   r   r   r   r{   r  r   r  s              r   test_remove_personaz%TestHandleControl.test_remove_persona'	  s   $$1$%A> % 

 -. 	T"",<#RS	T /w///x/////x////x//////w///w///////////3w333x33333x3333x333333w333w33333333333##)s#$))$))))$))))))s)))s))))))7)))7)))#)))$))))))))))	T 	Ts   LLc                    | j                  dgddi      }t        d      5 }|j                  ddd       ddd       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}}|j                  }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                  |      t	        j                  |      dz  }
dd|
iz  }t        t	        j                  |            dx}x}x}	}j                          y# 1 sw Y   xY w)uL   1명만 남았을 때 제거 시도 → 무시하고 그대로 유지한다.r   r   r  r  rT  r  Nr   z0%(py1)s in %(py5)s
{%(py5)s = %(py3)s.personas
}r	  r   r   r   r  rZ   r  r6   r  r  r  )r  r   r  r  rj   rk   ro   rl   rm   rn   rp   rq   r6   rS   )rT   r	  r  r   r   r   r   r   r{   r  r   r  s               r   test_remove_persona_last_onez.TestHandleControl.test_remove_persona_last_one5	  s   $$Z"A % 

 -. 	T,"",<#RS	T +7+++x+++++x++++x++++++7+++7+++++++++++##)s#$))$))))$))))))s)))s))))))7)))7)))#)))$))))))))))&&(	T 	Ts   I

Ic                    | j                  ddgddd      }t        d      5  |j                  dd	d
       ddd       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}}|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}}|j                  }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                  |      t	        j                  |
      dz  }dd|iz  }t        t	        j                  |            dx}x}x}}
y# 1 sw Y   xY w)uC   새 인원을 합류시키고 speak_counts에 0으로 추가한다.r   r   r  r  r  r  r  rY  r   r  Nr   r  r	  r   r   r   r   rZ   r   r   r   r`   r  r  r6   r  r  r  r  )rT   r	  r   r   r   r   r   ry   r   r   r{   r  r   r  s                 r   test_add_personaz"TestHandleControl.test_add_personaC	  s   $$)$%3 % 

 -. 	O""Mf#MN	O ))))v)))))v))))v))))))))))))))))))))##F+0q0+q0000+q000+000q0000000##)s#$))$))))$))))))s)))s))))))7)))7)))#)))$))))))))))	O 	Os   KKc           	      d   | j                  ddgddd      }t        d      5 }|j                  ddd	       d
d
d
       |j                  }|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                  |      t        j                  |      dz  }	dd|	iz  }
t        t        j                  |
            d
x}x}x}x}x}}|j                  }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                  |      t        j                  |      dz  }dd|iz  }t        t        j                  |            d
x}x}x}}j                          y
# 1 sw Y   nxY w)uP   이미 있는 인원을 추가하면 무시하고 중복 추가하지 않는다.r   r   r  r  r  r  r  rY  r  NrZ   )zj%(py8)s
{%(py8)s = %(py4)s
{%(py4)s = %(py2)s
{%(py2)s = %(py0)s.personas
}.count
}(%(py6)s)
} == %(py11)sr	  r\   rc   rd   r  r6   r  r  r  )r  r   r  r  rM  rj   rk   rl   rm   rn   ro   rp   rq   r6   rS   )rT   r	  r  rx   ry   rz   r{   r|   r}   r~   r   r   r   r  r   r  s                   r   test_add_persona_duplicatez,TestHandleControl.test_add_persona_duplicateQ	  s   $$)$%3 % 

 -. 	Q,""Mh#OP	Q 4%%4h4%h/414/14444/1444444w444w444444%444h444/444144444444##)s#$))$))))$))))))s)))s))))))7)))7)))#)))$))))))))))&&(	Q 	Qs   J%%J/c                 
   | j                  g ddddd      }t        d      5  |j                  dg dd	d
       ddd       |j                  }t	        |      }h 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                  |      t        j                  |      dz  }dd|iz  }t        t        j                  |            dx}x}x}}d}|j                  }||v}|st        j                  d|fd||f      t        j                  |      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      dz  }	dd|	iz  }
t        t        j                  |
            dx}x}}d}|j                  }||v}|st        j                  d|fd||f      t        j                  |      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      dz  }	dd|	iz  }
t        t        j                  |
            dx}x}}d}|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}}|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}}|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}}|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# 1 sw Y   3xY w)#u4   역할 필터를 적용해 personas를 교체한다.r   r   r   r  r   r  r  r  r^  )r   r   r  u	   백엔드)rK  r  r   Nr_  rZ   r  r4   r	  r  r  r  r   r   r  r   r   r   r   r   r   r   r   r   r`   r   r  )r  r   r  r  r4   rj   rk   rl   rm   rn   ro   rp   rq   r  )rT   r	  r   r   r{   r  r   r  r   r   r   ry   r   r   s                 r   test_filter_by_rolez%TestHandleControl.test_filter_by_role_	  sl   $$1$%A> % 

 -. 	"", <'	 ##Ds#$D(DD$(DDDDD$(DDDDDDDsDDDsDDDDDD7DDD7DDD#DDD$DDD(DDDDDDDD3w333x33333x3333x333333w333w333333333333w333x33333x3333x333333w333w333333333331W111v11111v1111v111111W111W11111111111##H-22-2222-222-2222222222##F+0q0+q0000+q000+000q0000000##H-22-2222-222-2222222222#	 	s   U..U8c           
         | j                  g ddddd      }t        d      5  |j                  dg dd	d
       ddd       |j                  }t	        |      }h 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                  |      t        j                  |      dz  }dd|iz  }t        t        j                  |            dx}x}x}}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}}|j                  }|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                  |      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}x}x}}y# 1 sw Y   ExY w)u1   팀 필터를 적용해 personas를 교체한다.r  r  r   r  r  r  re  )r   r   rT  rV  rX  u   2팀)rK  r  r:   N>   r   r   rT  rV  rX  rZ   r  r4   r	  r  r  r  r   r   r  r   r   r   r   )zx%(py11)s
{%(py11)s = %(py4)s
{%(py4)s = %(py2)s
{%(py2)s = %(py0)s.speak_counts
}.get
}(%(py6)s, -%(py8)s)
} == %(py14)s)r]   r^   r_   r`   ra   rb   r  zassert %(py16)spy16)r  r   r  r  r4   rj   rk   rl   rm   rn   ro   rp   rq   r  r   )rT   r	  r   r   r{   r  r   r  r   r   r   rx   ry   rz   r}   r|   r  rB  r  @py_format17s                       r   test_filter_by_teamz%TestHandleControl.test_filter_by_teamy	  sM   $$1$%A> % 

 -. 	"", N"	 ##Vs#$V(VV$(VVVVV$(VVVVVVVsVVVsVVVVVV7VVV7VVV#VVV$VVV(VVVVVVVV3w333x33333x3333x333333w333w33333333333##8#''8888'38q83q88883q888888w888w888#888'8888888883888q88888888	 	s   N  N
c                 J   | j                         }d|_        t        d      5 }|j                  ddd       d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                  |      t        j                  |      d
z  }dd|iz  }t        t        j                  |            d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                  |      d
z  }dd|iz  }t        t        j                  |            dx}x}}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}	}
y# 1 sw Y   bxY w)u:   max_auto_turns를 설정하고 auto_turns를 리셋한다.r  r  ri  r  r  NrZ   z6%(py2)s
{%(py2)s = %(py0)s.max_auto_turns
} == %(py5)sr	  r  r   r   r   r  r  r  r   r   r  r   r   r   r  r  r   r  max_auto_turnsrj   rk   rl   rm   rn   ro   rp   rq   r  r  rT   r	  r  rx   r   ry   r   r   r  r   r   r   s               r   test_set_auto_turnsz%TestHandleControl.test_set_auto_turns	  s   $$&-. 	K,"",<q#IJ	K %%**%****%******w***w***%**********!!&Q&!Q&&&&!Q&&&&&&w&&&w&&&!&&&Q&&&&&&&'') **1-a0	sisisii	K 	K   JJ"c                 J   | j                         }d|_        t        d      5 }|j                  ddd       d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                  |      t        j                  |      d
z  }dd|iz  }t        t        j                  |            d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                  |      d
z  }dd|iz  }t        t        j                  |            dx}x}}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}	}
y# 1 sw Y   bxY w)u7   count=99 → '계속 진행' 메시지를 전송한다.r  r  ri  rl  r  NrZ   r  r	  r  r   r   r   r  r  u   계속r   r   r  r   r   r   r  r  s               r   test_set_auto_turns_unlimitedz/TestHandleControl.test_set_auto_turns_unlimited	  s   $$&-. 	L,"",<r#JK	L %%++%++++%++++++w+++w+++%++++++++++!!&Q&!Q&&&&!Q&&&&&&w&&&w&&&!&&&Q&&&&&&&'') **1-a0	$x9$$$$x9$$$x$$$$$$9$$$9$$$$$$$	L 	Lr  )NN)r   r   r   r   r  r  r  r  r  r  r  r  r  r  r  r   r   r   r  r    s<    - %)*)*)349( %r   r  c                       e Zd ZdZd Zy)TestSendSystemMessageu(   send_system_message 메서드 테스트.c                     t               }|j                  dddddddddi      }d	|_        t        d
      5 }|j	                  d       ddd       j                  dd	d       y# 1 sw Y   xY w)uP   send_system_message는 '⚙️ 텍스트' 형식으로 Telegram에 전송한다.rs  r   r(  r  r7  r   r   r  rt  r  u   인원 조정 완료Nu   ⚙️ 인원 조정 완료)rG   r  r  r   send_system_messager  )rT   rF   r	  r  s       r   test_sends_with_gear_emojiz0TestSendSystemMessage.test_sends_with_gear_emoji	  s    _%%*(+!##%$&	 & 
 "-. 	@,''(>?	@ 	,,\7Dab	@ 	@s   A''A0N)r   r   r   r   r  r   r   r   r  r  	  s    2cr   r  )9r   builtinsrl   _pytest.assertion.rewrite	assertionrewriterj   r2   rP   r   r$  r   pathlibr   unittest.mockr   r   r   r   r   rQ   r   r   r/   ri   pathinsertintr@   rG   rI   r   r   r  r5  rP  rh  r~  fixturer  r  r  r  r  r.  rJ  rq  r  r  r  r  r!  rH  rs  r  r  r  r   r   r   <module>r     s  *   	 
    5 5 C"  bjjnn%57LMN#h.1NN ~chh&HHOOAs>*+5c 5p 3# 3#v;- ;-FkY kYfo, o,nX) X)@1J 1Jr$ $X\+ \+H  
 	 	"~+ ~+Lf, f,\'/ '/^\  \ Ho$ o$ne' e'Ze. e.Z.0 .0l\+ \+Hk* k*fOG OGnj0 j0d2B 2BvYY %Y" +#Y2 !3YB !CYR SYb %cYr +sYB CYR !SYb 	cYr %sYB +CYR SYb !cYr "sYB CYT " "%cY xl% l%hc cr   