
    is                     P   d Z ddlZddlmc mZ ddlZddlZddl	Z	ddl
Z
ddlmZmZmZ ddlmZ ddlmZmZ ddlZddlZej,                  j/                  d      Z e
j2                         Zeej,                  d<   dej,                  d<   d	ej,                  d
<   e	j6                  j9                  d e ee      j>                  j>                                e d      Z!eej,                  jE                  dd       neej,                  d<    G d d      Z# G d d      Z$ G d d      Z% G d d      Z& G d d      Z' G d d      Z( G d d      Z) G d d      Z* G d d      Z+ G d d      Z, G d  d!      Z- G d" d#      Z. G d$ d%      Z/e0d&k(  r ejb                  ed'g       yy)(u;   activity-watcher.py 테스트 스위트 (task-902.1 반영)    N)datetime	timedeltatimezone)Path)	MagicMockpatchWORKSPACE_ROOTtest_chat_idCOKACDIR_CHAT_IDtest_bot_tokenANU_BOT_TOKENzactivity-watcherc                   "    e Zd ZdZd Zd Zd Zy)TestLoadBotActivityu"   bot-activity.json 로드 테스트c           	         |dz  dz  dz  }|j                   j                  d       |j                  t        j                  dddd	d
dd	di             |t
        _        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}}|d   d   d   }d
}||k(  }|slt        j                  d|fd||f      t        j                  |      t        j                  |      dz  }dd|iz  }t        t        j                  |            dx}x}}y)u   유효한 JSON 로드memoryeventsbot-activity.jsonTparentsbotsidle2026-03-16T00:00:00Zstatussince
processingz2026-03-16T00:01:00Z)dev1dev2r   r   ==z%(py1)s == %(py4)spy1py4assert %(py6)spy6Nr   )parentmkdir
write_textjsondumpsawBOT_ACTIVITY_FILEload_bot_activity
@pytest_ar_call_reprcompare	_safereprAssertionError_format_explanation)	selftmp_pathbot_activity_fileresult@py_assert0@py_assert3@py_assert2@py_format5@py_format7s	            :/home/jay/workspace/scripts/tests/test_activity_watcher.pytest_load_valid_jsonz(TestLoadBotActivity.test_load_valid_json'   s6   $x/(:=PP  &&t&4$$JJ+1<R S+7BX Y		
  1%%'f~f%h/969/69999/6999/99969999999f~f%h/?<?/<????/<???/???<???????    c                    |dz  t         _        t         j                         }di i}||k(  }|st        j                  d|fd||f      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      dz  }dd|iz  }t        t        j                  |            d	x}}y	)
u#   파일이 없으면 빈 dict 반환znonexistent.jsonr   r   z%(py0)s == %(py3)sr7   py0py3assert %(py5)spy5N)r,   r-   r.   r/   r0   @py_builtinslocals_should_repr_global_namer1   r2   r3   r4   r5   r7   r:   @py_assert1@py_format4@py_format6s          r=   test_load_missing_filez*TestLoadBotActivity.test_load_missing_file<   s{    '*<<%%' "%v%%%%v%%%%%%v%%%v%%%%%%%%%%r?   c                    |dz  }|j                  d       |t        _        t        j                         }di i}||k(  }|st	        j
                  d|fd||f      dt        j                         v st	        j                  |      rt	        j                  |      ndt	        j                  |      dz  }dd	|iz  }t        t	        j                  |            d
x}}y
)u&   JSON 파싱 에러 시 빈 dict 반환r   zinvalid json {r   r   rA   r7   rB   rE   rF   N)r)   r,   r-   r.   r/   r0   rG   rH   rI   r1   r2   r3   )r4   r5   r6   r7   r:   rK   rL   rM   s           r=   test_load_invalid_jsonz*TestLoadBotActivity.test_load_invalid_jsonB   s    $'::$$%560%%' "%v%%%%v%%%%%%v%%%v%%%%%%%%%%r?   N)__name__
__module____qualname____doc__r>   rN   rP    r?   r=   r   r   $   s    ,@*&&r?   r   c                   :    e Zd ZdZd Zd Zd Zd Zd Zd Z	d Z
y	)
TestFindDoneFileu0   done 파일 찾기 테스트 (팀 기반 매칭)c           	      T   |dz  dz  }|j                  d       |dz  }|j                  d       |dz  dz  }|j                  j                  dd       |j                  t        j                  d	d
dddii             |t
        _        t        j                  t
        dt        |            5  t
        j                  d      }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}y# 1 sw Y   xY w)u<   task-timers.json 기반 팀 매칭으로 .done 파일 반환r   r   Tr   task-123.1.done{}task-timers.jsonr   exist_oktasks
task-123.1	dev2-teamrunningteam_idr   r	   r   Nr   z%(py0)s == %(py2)sr7   	done_filerC   py2zassert %(py4)sr$   r(   r)   r'   r*   r+   r,   
EVENTS_DIRr   objectstrfind_done_filer/   r0   rG   rH   rI   r1   r2   r3   )	r4   r5   
events_dirre   
timer_filer7   rK   @py_format3r;   s	            r=   test_find_done_file_team_basedz/TestFindDoneFile.test_find_done_file_team_basedO   sH   (83
&!22	T" (+==
t<JJ$'2&/'		
 #\\".H> 	/&&v.F	/ """"v""""""v"""v""""""""""""""""	/ 	/s   0FF'c           	         |dz  dz  }|j                  d       |dz  dz  }|j                  j                  dd       |j                  t        j                  ddd	d
dii             |t
        _        t        j                  t
        dt        |            5  t
        j                  d      }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)!   done 파일 없으면 None 반환r   r   Tr   r[   r\   r^   
task-456.1r`   ra   rb   r	   r   Nisz%(py0)s is %(py3)sr7   rB   rE   rF   r(   r'   r)   r*   r+   r,   ri   r   rj   rk   rl   r/   r0   rG   rH   rI   r1   r2   r3   	r4   r5   rm   rn   r7   r:   rK   rL   rM   s	            r=   test_find_done_file_not_existsz/TestFindDoneFile.test_find_done_file_not_existsl   s   (83
& (+==
t<JJ$'2&/'		
 #\\".H> 	/&&v.F	/ v~vvv	/ 	/s   EE'c           	      $   |dz  dz  }|j                  d       |dz  j                  d       |dz  j                  d       |dz  d	z  }|j                  j                  dd
       |j                  t        j                  dddddii             |t
        _        t        j                  t
        dt        |            5  t
        j                  d      }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)uC   .done.notified, .done.acked 등이 있으면 해당 .done은 무시r   r   Tr   rY   rZ   task-123.1.done.notified r[   r\   r^   r_   r`   ra   rb   r	   r   Nrt   rv   r7   rB   rE   rF   rh   rx   s	            r=   0test_find_done_file_ignores_processed_extensionszATestFindDoneFile.test_find_done_file_ignores_processed_extensions   sE   (83
&	'	'33D9	0	0<<R@(+==
t<JJ$'2&/'		
 #\\".H> 	/&&v.F	/ v~vvv	/ 	/   FFc           	      $   |dz  dz  }|j                  d       |dz  j                  d       |dz  j                  d       |dz  d	z  }|j                  j                  dd
       |j                  t        j                  dddddii             |t
        _        t        j                  t
        dt        |            5  t
        j                  d      }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)u   .done.acked 존재 시 무시r   r   Tr   rY   rZ   ztask-123.1.done.ackedr|   r[   r\   r^   r_   r`   ra   rb   r	   r   Nrt   rv   r7   rB   rE   rF   rh   rx   s	            r=   !test_find_done_file_ignores_ackedz2TestFindDoneFile.test_find_done_file_ignores_acked   sE   (83
&	'	'33D9	-	-99"=(+==
t<JJ$'2&/'		
 #\\".H> 	/&&v.F	/ v~vvv	/ 	/r~   c                    |dz  dz  }|j                  d       |dz  j                  d       |dz  dz  }|j                  j                  dd       |j                  t        j                  d	i i             |t
        _        t        j                  t
        d
t        |            5  t
        j                  d      }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)u&   활성 태스크 없으면 None 반환r   r   Tr   rY   rZ   r[   r\   r^   r	   r   Nrt   rv   r7   rB   rE   rF   rh   rx   s	            r=   "test_find_done_file_no_active_taskz3TestFindDoneFile.test_find_done_file_no_active_task   s   (83
&	'	'33D9 (+==
t<djj'278"\\".H> 	/&&v.F	/ v~vvv	/ 	/s   )E--E6c                    |dz  dz  }|j                  d       |t        _        t        j                  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)u3   anu는 BOT_TEAM_MAP에서 None이므로 항상 Noner   r   Tr   anuNrt   rv   r7   rB   rE   rF   )r(   r,   ri   rl   r/   r0   rG   rH   rI   r1   r2   r3   r4   r5   rm   r7   r:   rK   rL   rM   s           r=   $test_find_done_file_anu_returns_nonez5TestFindDoneFile.test_find_done_file_anu_returns_none   s    (83
&"""5)v~vvvr?   c           
         dD ]  }|dz  dz  }|j                  dd       |d| dz  }|j                  d       |dz  d	z  }|j                  j                  dd       |j                  t        j                  d
d| dd| dddii             |t
        _        t        j                  t
        dt        |            5  t
        j                  d|       }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  }t        j"                  d| d      dz   d|iz  }	t%        t        j&                  |	            d}|j)                           y# 1 sw Y   xY w)u   dev4~dev8 팀 매칭 지원)               r   r   Tr\   ztask-z	00.1.donerZ   r[   r^   z00.1devz-teamra   rb   r	   Nr   rd   r7   re   rf   u    팀 매칭 실패z
>assert %(py4)sr$   )r(   r)   r'   r*   r+   r,   ri   r   rj   rk   rl   r/   r0   rG   rH   rI   r1   _format_assertmsgr2   r3   unlink)
r4   r5   dev_numrm   re   rn   r7   rK   ro   r;   s
             r=    test_find_done_file_dev4_to_dev8z1TestFindDoneFile.test_find_done_file_dev4_to_dev8   s   & 	G!H,x7JTD9"uWIY%??I  &!H,/AAJ##D4#@!!

#G9D1-0	+?*34"	 'BMb"2CMB <**S	?;< Y&III6YIIIIII6III6IIIIIIYIIIYIIII#gY6H(IIIIIII 9	,< <s   G!!G+	N)rQ   rR   rS   rT   rp   ry   r}   r   r   r   r   rU   r?   r=   rW   rW   L   s(    :#:688"r?   rW   c                   (    e Zd ZdZd Zd Zd Zd Zy)TestGetActiveTaskForTeamu)   get_active_task_for_team 함수 테스트c           	         |dz  dz  }|j                   j                  dd       |j                  t        j                  ddddd	ddd
i             t        j                  t        dt        |            5  t        j                  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)u)   팀에 매칭되는 활성 task_id 반환r   r[   Tr\   r^   	dev1-teamra   rb   r`   )
task-100.1z
task-200.1r	   r   Nr   r   rA   r7   rB   rE   rF   r'   r(   r)   r*   r+   r   rj   r,   rk   get_active_task_for_teamr/   r0   rG   rH   rI   r1   r2   r3   r4   r5   rn   r7   r:   rK   rL   rM   s           r=   #test_returns_task_for_matching_teamz<TestGetActiveTaskForTeam.test_returns_task_for_matching_team   s    (+==
t<JJ2=&S2=&S		
 \\".H> 	9008F	9 &%v%%%%v%%%%%%v%%%v%%%%%%%%%%	9 	9s   9D>>Ec                    |dz  dz  }|j                   j                  dd       |j                  t        j                  di i             t        j                  t        dt        |            5  t        j                  d      }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)u    매칭 팀 없으면 None 반환r   r[   Tr\   r^   r	   dev9Nrt   rv   r7   rB   rE   rF   r   r   s           r=   test_returns_none_when_no_matchz8TestGetActiveTaskForTeam.test_returns_none_when_no_match  s    (+==
t<djj'278\\".H> 	9008F	9 v~vvv	9 	9s   0D44D=c           	         |dz  dz  }|j                   j                  dd       |j                  t        j                  ddddd	ii             t        j                  t        d
t        |            5  t        j                  d      }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)u%   status != running 태스크는 무시r   r[   Tr\   r^   r   r   donerb   r	   r   Nrt   rv   r7   rB   rE   rF   r   r   s           r=   test_ignores_non_running_tasksz7TestGetActiveTaskForTeam.test_ignores_non_running_tasks  s    (+==
t<JJ$+&P	
 \\".H> 	9008F	9 v~vvv	9 	9s   5D99Ec                    t        j                  t        dt        |            5  t        j	                  d      }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)
u&   task-timers.json 없으면 None 반환r	   r   Nrt   rv   r7   rB   rE   rF   )r   rj   r,   rk   r   r/   r0   rG   rH   rI   r1   r2   r3   rJ   s          r=   #test_returns_none_when_file_missingz<TestGetActiveTaskForTeam.test_returns_none_when_file_missing/  s    \\".H> 	9008F	9 v~vvv	9 	9s   C))C2N)rQ   rR   rS   rT   r   r   r   r   rU   r?   r=   r   r      s    3&(	&r?   r   c                   "    e Zd ZdZd Zd Zd Zy)TestCheckAlreadyNotifieduQ   이미 알림 보냈는지 확인 테스트 (.done.notified 마커 파일 기반)c                    |dz  dz  }|j                  d       |dz  }|j                          |t        _        t        j	                  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)u,   .done.notified 마커 파일 존재 시 Truer   r   Tr   r{   r_   rt   rv   r7   rB   rE   rF   Nr(   touchr,   ri   check_already_notifiedr/   r0   rG   rH   rI   r1   r2   r3   )	r4   r5   rm   notified_filer7   r:   rK   rL   rM   s	            r=   #test_already_notified_marker_existsz<TestCheckAlreadyNotified.test_already_notified_marker_exists:  s    (83
&"%??"**<8v~vvvr?   c                    |dz  dz  }|j                  d       |t        _        t        j                  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)u,   .done.notified 마커 파일 없으면 Falser   r   Tr   r_   Frt   rv   r7   rB   rE   rF   N)r(   r,   ri   r   r/   r0   rG   rH   rI   r1   r2   r3   r   s           r=    test_not_notified_marker_missingz9TestCheckAlreadyNotified.test_not_notified_marker_missingE  s    (83
&"**<8vvvvr?   c                    |dz  dz  }|j                  d       |dz  j                          |t        _        t        j	                  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)u)   다른 task_id의 마커는 영향 없음r   r   Tr   ztask-999.1.done.notifiedr_   Frt   rv   r7   rB   rE   rF   Nr   r   s           r=   #test_different_task_id_not_confusedz<TestCheckAlreadyNotified.test_different_task_id_not_confusedN  s    (83
&	0	0779"**<8vvvvr?   N)rQ   rR   rS   rT   r   r   r   rU   r?   r=   r   r   7  s    [		r?   r   c                   :    e Zd ZdZd Zd Zd Zd Zd Zd Z	d Z
y	)
TestSendTelegramNotificationu4   텔레그램 알림 테스트 (requests.post 방식)c                    t               }d|_        t        j                  dddi      5  t        d|      5 }t        j                  dd      }d	d	d	       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}}j                          |j                  d   }|j!                  di       }	|	j                   }d}
 ||
      }d}||k(  }|st        j                  d|fd||f      dt        j                         v st        j                  |	      rt        j                  |	      ndt        j                  |      t        j                  |
      t        j                  |      t        j                  |      dz  }dd|iz  }t        t        j                  |            d	x}x}
x}x}}|	j                   }d}
 ||
      }d}||k(  }|st        j                  d|fd||f      dt        j                         v st        j                  |	      rt        j                  |	      ndt        j                  |      t        j                  |
      t        j                  |      t        j                  |      dz  }dd|iz  }t        t        j                  |            d	x}x}
x}x}}y	# 1 sw Y   xY w# 1 sw Y   xY w)u!   알림 전송 성공 (200 응답)   
os.environr   
test-tokenrequests.postreturn_valuetest messagechat_idNTrt   rv   r7   rB   rE   rF      r*   textr   zI%(py6)s
{%(py6)s = %(py2)s
{%(py2)s = %(py0)s.get
}(%(py4)s)
} == %(py9)spayloadrC   rg   r$   r&   py9assert %(py11)spy11)r   status_coder   dictr,   send_telegram_notificationr/   r0   rG   rH   rI   r1   r2   r3   assert_called_once	call_argsget)r4   	mock_resp	mock_postr7   r:   rK   rL   rM   call_kwargsr   r9   @py_assert5@py_assert8@py_assert7@py_format10@py_format12s                   r=   test_send_notification_successz;TestSendTelegramNotification.test_send_notification_success]  s'   K	 #	ZZ&EF 	RY? R966~yQR	R v~vvv$$&))!,//&"-{{464{6"4n4"n4444"n444444w444w444{4446444"444n4444444{{292{9%22%2222%222222w222w222{2229222%22222222222R R	R 	Rs"   L?L2L?2L<	7L??M	c                 N   t         j                  j                         D ci c]  \  }}|dk7  s|| }}}t        j                  d|d      5  t
        j                  dd      }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c c}}w # 1 sw Y   xY w)u$   ANU_BOT_TOKEN 없으면 False 반환r   r   T)clearr   r   NFrt   rv   r7   rB   rE   rF   )osenvironitemsr   r   r,   r   r/   r0   rG   rH   rI   r1   r2   r3   )	r4   kvenvr7   r:   rK   rL   rM   s	            r=   -test_send_notification_no_token_returns_falsezJTestSendTelegramNotification.test_send_notification_no_token_returns_falsem  s     "

 0 0 2K1a?6Jq!tKKZZc6 	N22>9MF	Nvvvv L	N 	Ns   DDDD$c                 H   t               }d|_        i |_        t        j                  dddi      5  t        d|      5  t
        j                  dd      }d	d	d	       d	d	d	       d
}|u }|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)u   400 응답 시 False 반환i  r   r   r   r   r   r   r   NFrt   rv   r7   rB   rE   rF   )r   r   headersr   r   r,   r   r/   r0   rG   rH   rI   r1   r2   r3   )r4   r   r7   r:   rK   rL   rM   s          r=   &test_send_notification_failure_non_200zCTestSendTelegramNotification.test_send_notification_failure_non_200t  s    K	 #		ZZ&EF 	RY? R66~yQR	R vvvvR R	R 	Rs"   DDDD	DD!c                 H   t               }d|_        ddi|_        t               }d|_        t        j                  dddi      5  t        d||g	      5 }t        d
      5  t
        j                  dd      }ddd       ddd       ddd       d}|u }|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   xY w# 1 sw Y   xY w# 1 sw Y   xY w)u   429 응답 시 재시도i  zRetry-After1r   r   r   r   r   side_effect
time.sleepr   r   NTrt   rv   r7   rB   rE   rF      r   )z2%(py2)s
{%(py2)s = %(py0)s.call_count
} == %(py5)sr   )rC   rg   rF   zassert %(py7)spy7)r   r   r   r   r   r,   r   r/   r0   rG   rH   rI   r1   r2   r3   
call_count)r4   mock_429mock_200r   r7   r:   rK   rL   rM   @py_assert4r9   @py_format8s               r=    test_send_notification_429_retryz=TestSendTelegramNotification.test_send_notification_429_retry  s   ;")3/;"ZZ&EF 	VXx4HI VY<( V::>9UFVV	V
 v~vvv##(q(#q((((#q((((((y(((y(((#(((q(((((((	V VV V	V 	Vs<   HH
 G=7H
?H=HH

H	HH!c                 ~   t        j                  dddi      5  t        dt        j                  d            5  t        d      5  t        j                  dd	      }d
d
d
       d
d
d
       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# 1 sw Y   xY w# 1 sw Y   xY w)u(   RequestException 시 재시도 후 Falser   r   r   r   errr   r   r   r   NFrt   rv   r7   rB   rE   rF   )r   r   real_requestsRequestExceptionr,   r   r/   r0   rG   rH   rI   r1   r2   r3   )r4   r7   r:   rK   rL   rM   s         r=   (test_send_notification_request_exceptionzETestSendTelegramNotification.test_send_notification_request_exception  s    ZZ&EF 	VM4R4RSX4YZ V<( V::>9UFVV	V vvvvV VV V	V 	Vs:   !D3D'DD'%D3D$ D''D0	,D33D<c                    t               }d|_        t        j                  dddi      5  t        d|      5 }t        j                  dd       d	d	d	       d	d	d	       j                  d
   }|j                  }d} ||      }d}||k(  }|st        j                  d|fd||f      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      t        j                  |      t        j                  |      dz  }	dd|	iz  }
t        t        j                  |
            d	x}x}x}x}}y	# 1 sw Y    xY w# 1 sw Y   %xY w)u   timeout=10 사용 확인r   r   r   r   r   r   r   r   Nr   timeout
   r   r   r   r   r   r   )r   r   r   r   r,   r   r   r   r/   r0   rG   rH   rI   r1   r2   r3   )r4   r   r   r   rK   r9   r   r   r   r   r   s              r=   "test_send_notification_timeout_10sz?TestSendTelegramNotification.test_send_notification_timeout_10s  s   K	 #	ZZ&EF 	IY? I9--niHI	I  ))!,/y/y)/R/)R////)R//////{///{//////y///)///R////////	I I	I 	Is"   E:E-E:-E7	2E::Fc                 P   t               }d|_        t        j                  dddi      5  t        d|      5  t        d      5 }t        j                  dd	       d
d
d
       d
d
d
       d
d
d
       j                          y
# 1 sw Y   *xY w# 1 sw Y   .xY w# 1 sw Y   2xY w)uA   cokacdir 명령을 사용하지 않음 (subprocess.run 미호출)r   r   r   r   r   r   zsubprocess.runr   r   N)r   r   r   r   r,   r   assert_not_called)r4   r   mock_runs      r=   test_no_cokacdir_in_sendz5TestSendTelegramNotification.test_no_cokacdir_in_send  s    K	 #	ZZ&EF 	MY? M+, M11.)LMM	M
 	""$M MM M	M 	Ms:   BBBB#BB	BB	BB%N)rQ   rR   rS   rT   r   r   r   r   r   r   r   rU   r?   r=   r   r   Z  s(    >3 
)"
0
%r?   r   c                   "    e Zd ZdZd Zd Zd Zy)TestBotTeamMapu   BOT_TEAM_MAP 확장 확인c           
      F   dD ]  }t         j                  }|j                  } ||      }||k(  }|sdt        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                  |      rt        j                  |      nd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}x}x}} y
)u   dev1~dev3 매핑 확인)r   r   dev3r   zk%(py7)s
{%(py7)s = %(py4)s
{%(py4)s = %(py2)s
{%(py2)s = %(py0)s.BOT_TEAM_MAP
}.get
}(%(py5)s)
} == %(py9)sr,   r   rC   rg   r$   rF   r   r       매핑 없음
>assert %(py11)sr   Nr,   BOT_TEAM_MAPr   r/   r0   rG   rH   rI   r1   r   r2   r3   r4   r   rK   r9   @py_assert6r   r   r   s           r=   test_dev1_to_dev3_mappedz'TestBotTeamMap.test_dev1_to_dev3_mapped  s    + 	KC??J?&&J&s+J+s2JJJ+sJJJJJJ2JJJ2JJJ?JJJ&JJJJJJsJJJsJJJ+JJJJJJsJJJsJJJJse>4JJJJJJJJ	Kr?   c           
      F   dD ]  }t         j                  }|j                  } ||      }||k(  }|sdt        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                  |      rt        j                  |      nd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}x}x}} y
)u   dev4~dev8 매핑 확인)dev4dev5dev6dev7dev8r   r   r,   r   r   r   r   r   Nr   r  s           r=   test_dev4_to_dev8_mappedz'TestBotTeamMap.test_dev4_to_dev8_mapped  s    ; 	KC??J?&&J&s+J+s2JJJ+sJJJJJJ2JJJ2JJJ?JJJ&JJJJJJsJJJsJJJ+JJJJJJsJJJsJJJJse>4JJJJJJJJ	Kr?   c           	      Z   t         j                  }|j                  }d} ||      }d}||u }|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}}y)	u   anu는 None으로 매핑r   Nrt   )zl%(py8)s
{%(py8)s = %(py4)s
{%(py4)s = %(py2)s
{%(py2)s = %(py0)s.BOT_TEAM_MAP
}.get
}(%(py6)s)
} is %(py11)sr,   )rC   rg   r$   r&   py8r   zassert %(py13)spy13)r,   r   r   r/   r0   rG   rH   rI   r1   r2   r3   )	r4   rK   r9   r   r   @py_assert10@py_assert9r   @py_format14s	            r=   test_anu_mapped_to_nonez&TestBotTeamMap.test_anu_mapped_to_none  s    1""151"5)1T1)T1111)T111111r111r111111"1115111)111T11111111r?   N)rQ   rR   rS   rT   r  r
  r  rU   r?   r=   r   r     s    $K
K
2r?   r   c                   (    e Zd ZdZd Zd Zd Zd Zy)TestProcessingToIdleDetectionu2   processing → idle 전환 감지 통합 테스트c           	      B   |dz  dz  dz  }|j                   j                  d       |j                  t        j                  dddd	d
ii             |t
        _        t
        j                         }i }|j                  di       j                         D ]  \  }}|j                  dd      ||<    |j                         }g }|j                         D ]:  \  }}	|j                  |d      }
|dk(  r|
dk(  s$|	dk(  s*|j                  |       < t        |      }d}||k(  }|st        j                  d|fd||f      dt        j                          v st        j"                  t              rt        j$                  t              nddt        j                          v st        j"                  |      rt        j$                  |      ndt        j$                  |      t        j$                  |      dz  }dd|iz  }t'        t        j(                  |            dx}x}}y)u3   idle → idle (변화 없음) 시 알림 안 보냄r   r   r   Tr   r   r   r   r   r   r   r   r   r   r   z0%(py3)s
{%(py3)s = %(py0)s(%(py1)s)
} == %(py6)slennotifications_sentrC   r#   rD   r&   assert %(py8)sr  Nr'   r(   r)   r*   r+   r,   r-   r.   r   r   copyappendr  r/   r0   rG   rH   rI   r1   r2   r3   )r4   r5   r6   prev_activityprev_statusesbot_namebot_datacurr_statusesr  curr_statusprev_statusr:   r   r   r<   @py_format9s                   r=   !test_idle_to_idle_no_notificationz?TestProcessingToIdleDetection.test_idle_to_idle_no_notification  s   $x/(:=PP  &&t&4$$JJFE[)\ ]^_	
  1,,."/"3"3FB"?"E"E"G 	EHh&.ll8V&DM(#	E &**,%2%8%8%: 	4!Hk'++Hf=K5 l*{f/D"))(3	4 %&+!+&!++++&!++++++s+++s++++++%+++%+++&+++!+++++++r?   c           	      \   |dz  dz  }|j                  d       |dz  }|j                  d       |dz  dz  }|j                  j                  dd       |j                  t        j                  d	d
dddii             |t
        _        |dz  t
        _        ddi}ddi}t        j                  t
        dt        |            5  t        j                  t
        d      5 }d|_        |j                         D ]y  \  }}	|j                  |d      }
|dk(  r|
dk(  s$|	dk(  s*t
        j                  |      }|sB|j                  }t
        j!                  |      rdt
        j#                  |d       { |j%                          ddd       ddd       y# 1 sw Y   xY w# 1 sw Y   yxY w)u,   processing → idle 전환 시 알림 전송r   r   Tr   rY   rZ   r[   r\   r^   r_   r`   ra   rb   done-protocol.logr   r   r   r	   r   r   r   N)r(   r)   r'   r*   r+   r,   ri   DONE_PROTOCOL_LOGr   rj   rk   r   r   r   rl   stemr   r   r   )r4   r5   rm   re   rn   r  r!  mock_notifyr  r"  r#  
found_donetask_ids                r=   *test_processing_to_idle_sends_notificationzHTestProcessingToIdleDetection.test_processing_to_idle_sends_notification  s   (83
&!22	T"(+==
t<JJ$'2&/'		
 #'*==.(\\".H> 	1b">? 1;+/(-:-@-@-B 	R)Hk"/"3"3Hf"EK5( "l2{f7L%'%6%6x%@
%&0ooG#%#<#<W#E " = =gy Q	R ..01	1 	11 1	1 	1s<   F"!;FF#F;!F(FF"F	F""F+c                    ddi}ddi}g }|j                         D ]:  \  }}|j                  |d      }|dk(  r|dk(  s$|dk(  s*|j                  |       < t        |      }d}||k(  }	|	st	        j
                  d|	fd||f      dt        j                         v st	        j                  t              rt	        j                  t              nddt        j                         v st	        j                  |      rt	        j                  |      ndt	        j                  |      t	        j                  |      d	z  }
d
d|
iz  }t        t	        j                  |            dx}x}	}y)u    아누(anu) 상태 변화 무시r   r   r   r   r   r  r  r  r  r  r  N)r   r   r  r  r/   r0   rG   rH   rI   r1   r2   r3   )r4   r  r!  r  r  r"  r#  r:   r   r   r<   r$  s               r=   test_anu_status_change_ignoredz<TestProcessingToIdleDetection.test_anu_status_change_ignored  s   -%2%8%8%: 	4!Hk'++Hf=K5 l*{f/D"))(3	4 %&+!+&!++++&!++++++s+++s++++++%+++%+++&+++!+++++++r?   c                    |dz  dz  }|j                  d       |dz  dz  }|j                  j                  dd       |j                  t        j                  di i             |t
        _        t        j                  t
        dt        |            5  t
        j                  d	      }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)rr   r   r   Tr   r[   r\   r^   r	   r   Nrt   rv   r7   rB   rE   rF   rw   rx   s	            r=   test_no_done_file_logs_onlyz9TestProcessingToIdleDetection.test_no_done_file_logs_only  s    (83
&(+==
t<djj'278"\\".H> 	/&&v.F	/v~vvv	/ 	/s   EE"N)rQ   rR   rS   rT   r%  r-  r/  r1  rU   r?   r=   r  r    s    <,6+1Z,r?   r  c                       e Zd ZdZd Zy)TestInitialStateu   초기 상태 로드 테스트c           	      B   |dz  dz  dz  }|j                   j                  d       |j                  t        j                  dddd	d
ii             |t
        _        t
        j                         }i }|j                  di       j                         D ]  \  }}|j                  dd      ||<    |j                         }g }|j                         D ]:  \  }}	|j                  |d      }
|dk(  r|
dk(  s$|	dk(  s*|j                  |       < t        |      }d}||k(  }|st        j                  d|fd||f      dt        j                          v st        j"                  t              rt        j$                  t              nddt        j                          v st        j"                  |      rt        j$                  |      ndt        j$                  |      t        j$                  |      dz  }dd|iz  }t'        t        j(                  |            dx}x}}y)u3   시작 시 초기 상태 로드로 오알림 방지r   r   r   Tr   r   r   r   r   r   r   r   r   r   r   r  r  transitionsr  r  r  Nr  )r4   r5   r6   r  r  r  r   r!  r5  r"  r#  r:   r   r   r<   r$  s                   r=   *test_initial_state_prevents_false_positivez;TestInitialState.test_initial_state_prevents_false_positive0  s   $x/(:=PP  &&t&4$$JJFE[)\ ]^_	
  1,,."/"3"3FB"?"E"E"G 	EHh&.ll8V&DM(#	E &**,%2%8%8%: 	-!Hk'++Hf=K5 l*{f/D""8,	- ;$1$1$$$$1$$$$$$s$$$s$$$$$$;$$$;$$$$$$1$$$$$$$r?   N)rQ   rR   rS   rT   r6  rU   r?   r=   r3  r3  -  s
    (%r?   r3  c                       e Zd ZdZd Zy)TestLogProtocolu   로그 기록 테스트c                 N   |d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}}|j                         }d}||v }|st        j                  d|fd||f      t        j                  |      d	t	        j
                         v st        j                  |      rt        j                  |      nd	d
z  }	dd|	iz  }
t        t        j                  |
            dx}}d}||v }|st        j                  d|fd||f      t        j                  |      d	t	        j
                         v st        j                  |      rt        j                  |      nd	d
z  }	dd|	iz  }
t        t        j                  |
            dx}}y)u   로그 파일에 기록r'  r   Aassert %(py4)s
{%(py4)s = %(py2)s
{%(py2)s = %(py0)s.exists
}()
}log_filerC   rg   r$   Ninz%(py1)s in %(py3)scontentr#   rD   rE   rF   z[activity-watcher])r,   r(  log_protocolexistsrG   rH   r/   rI   r1   r2   r3   	read_textr0   )r4   r5   r;  rK   r9   r;   r@  r8   r:   rL   rM   s              r=   test_log_protocol_writesz(TestLogProtocol.test_log_protocol_writesO  s>   11'
'         x   x             $$&(~((((~(((~((((((((((((((((#.#w....#w...#......w...w.......r?   N)rQ   rR   rS   rT   rE  rU   r?   r=   r8  r8  L  s
    !
/r?   r8  c                   .    e Zd ZdZd Zd Zd Zd Zd Zy)TestExtractReportSummaryu!   보고서 요약 추출 테스트c                 x   |dz  dz  }|j                  d       |dz  }d}|j                  |       t        |      t        _        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}||v }|st        j                  d	|fd
||f      t        j                  |      dt        j                         v st        j                  |      rt        j                  |      nddz  }dd|iz  }	t        t        j                  |	            dx}}d}||v }|st        j                  d	|fd
||f      t        j                  |      dt        j                         v st        j                  |      rt        j                  |      nddz  }dd|iz  }	t        t        j                  |	            dx}}d}||v }|st        j                  d	|fd
||f      t        j                  |      dt        j                         v st        j                  |      rt        j                  |      nddz  }dd|iz  }	t        t        j                  |	            dx}}d}||v }|st        j                  d	|fd
||f      t        j                  |      dt        j                         v st        j                  |      rt        j                  |      nddz  }dd|iz  }	t        t        j                  |	            dx}}y)u)   SCQA 형식 보고서에서 S와 A 추출r   reportsTr   ztask-123.1.mdu@  # task-123.1 완료 보고서

## SCQA

**S**: 사용자가 로그인할 때 500 에러가 발생했다.

**C**: 프로덕션 환경에서 재현이 어렵다.

**Q**: 어떻게 빠르게 원인을 파악할 수 있는가?

**A**: 로그 분석 도구를 도입하여 에러 추적을 자동화했다.

## 구현 상세
r_   r   r=  r?  r7   rA  rE   rF   NS:u
   500 에러zA:u   로그 분석r(   r)   rk   r,   r	   extract_report_summaryr/   r0   r1   rG   rH   rI   r2   r3   
r4   r5   reports_dirreport_filereport_contentr7   r8   r:   rL   rM   s
             r=   test_extract_with_scqaz/TestExtractReportSummary.test_extract_with_scqa_  su   )I5$'!O3 	~.M**<@%|v%%%%|v%%%|%%%%%%v%%%v%%%%%%%vvvtv~tvtvv%|v%%%%|v%%%|%%%%%%v%%%v%%%%%%%tv~tvtvv(&((((&(((((((((&(((&(((((((r?   c                    t        |      t        _        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}}g }t        |      }||v }	|	}|	sd}
|
|v }|}|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                  |      dt        j                         v st	        j                  |      rt	        j                  |      nddz  }dd|iz  }|j                  |       |	st	        j
                  dfd
|f      t	        j                  |
      dt        j                         v st	        j                  |      rt	        j                  |      nddz  }dd|iz  }|j                  |       t	        j                  |d      i z  }dd|iz  }t        t	        j                  |            d
x}x}x}x}	x}
}y
)u9   보고서 파일 없을 때 폴백 - 절대 경로 사용z
task-404.1r   u
   보고서:r=  r?  r7   rA  rE   rF   Nz/home/jay/workspace)z0%(py5)s
{%(py5)s = %(py2)s(%(py3)s)
} in %(py7)srk   r5   )rg   rD   rF   r   z%(py9)sr   )z%(py12)s in %(py14)s)py12py14z%(py16)spy16r   zassert %(py19)spy19)rk   r,   r	   rL  r/   r0   r1   rG   rH   rI   r2   r3   r  _format_boolop)r4   r5   r7   r8   r:   rL   rM   rK   r   r  @py_assert11@py_assert13r   r   @py_format15@py_format17@py_format18@py_format20s                     r=   test_extract_without_reportz4TestExtractReportSummary.test_extract_without_report  s   M**<@%|v%%%%|v%%%|%%%%%%v%%%v%%%%%%%%|v%%%%|v%%%|%%%%%%v%%%v%%%%%%%Is8}I}&I*?I*?6*IIIII}IIIIIIsIIIsIIIIII8III8III}IIIIIIIIIIIIIIII*?6III*?IIIIII6III6IIIIIIIIIIIIIIr?   c                 P   |dz  dz  }|j                  d       |dz  }d}|j                  |       t        |      t        _        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}}y)u:   SCQA 형식이 아닌 보고서도 크래시 없이 처리r   rI  Tr   ztask-456.1.mduI   # task-456.1 완료

## 작업 내용
- 버그 수정
- 테스트 추가
rs   r   r=  r?  r7   rA  rE   rF   NrK  rM  s
             r=   test_extract_non_scqa_reportz5TestExtractReportSummary.test_extract_non_scqa_report  s   )I5$'!O3 	~.M**<@%|v%%%%|v%%%|%%%%%%v%%%v%%%%%%%vvvr?   c                    |dz  dz  }|j                  d       |dz  }d}|j                  |       t        |      t        _        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}}y)u   수정 파일 수 포함r   rI  Tr   ztask-789.1.mduj   # task-789.1 완료

## SCQA

**S**: 문제 발생

**A**: 해결 완료

## 수정 내역
- 수정: 5건
z
task-789.1r   u   수정: 5건r=  r?  r7   rA  rE   rF   NrK  rM  s
             r=   test_extract_with_files_countz6TestExtractReportSummary.test_extract_with_files_count  s    )I5$'!O3
 	~.M**<@'~''''~'''~''''''''''''''''r?   c                 ^   |dz  dz  }|j                  d       |dz  }d}d| d}|j                  |       t        |      t        _        t        j                  d	d
      }|j                  d      }|D cg c]  }|j                  d      s| c}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c c}w )u!   긴 텍스트는 150자로 잘림r   rI  Tr   ztask-long.1.mdAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAu&   # task-long.1 완료

## SCQA

**S**: u   

**A**: 해결
ztask-long.1r   
rJ  r      )<=)z0%(py3)s
{%(py3)s = %(py0)s(%(py1)s)
} <= %(py6)sr  s_liner  r  r  N)r(   r)   rk   r,   r	   rL  split
startswithr  r/   r0   rG   rH   rI   r1   r2   r3   )r4   r5   rN  rO  	long_textrP  r7   lineslinerh  r:   r   r   r<   r$  s                  r=    test_extract_long_text_truncatedz9TestExtractReportSummary.test_extract_long_text_truncated  s8   )I5$'!$44	 	{ 	 	~.M**=&AT"#(B4DOOD,A$B1E6{!c!{c!!!!{c!!!!!!s!!!s!!!!!!6!!!6!!!{!!!c!!!!!!! Cs   8F*F*N)	rQ   rR   rS   rT   rQ  r^  r`  rb  rn  rU   r?   r=   rG  rG  \  s     +)@J ((0"r?   rG  c                   (    e Zd ZdZd Zd Zd Zd Zy)TestCheckAndRecoverStuckBotsAWuP   activity-watcher.py의 check_and_recover_stuck_bots 테스트 (워치독 통합)c                    t        j                  t        j                        t	        d      z
  j                  d      }ddd|dii}t        j                  |      }d}||k(  }|st        j                  d	|fd
||f      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      dz  }dd|iz  }t        t        j                  |            dx}}|d   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+   40분 경과 processing 봇 → idle 전환(   minutes%Y-%m-%dT%H:%M:%SZr   r   r   r   r   r   rA   	recoveredrB   rE   rF   Nr   r   r!   r"   r%   r&   r   nowr   utcr   strftimer,   check_and_recover_stuck_botsr/   r0   rG   rH   rI   r1   r2   r3   )r4   forty_min_agodatarv  r:   rK   rL   rM   r8   r9   r;   r<   s               r=   test_stuck_bot_recoveredz7TestCheckAndRecoverStuckBotsAW.test_stuck_bot_recovered  s   !hll3i6KKUUVjkL=!QRS33D9	yA~yAyyAF|F#H-77-7777-777-7777777777r?   c                    t        j                  t        j                        t	        d      z
  j                  d      }ddd|dii}t        j                  |      }d}||k(  }|st        j                  d	|fd
||f      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      dz  }dd|iz  }t        t        j                  |            dx}}|d   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&   10분 경과 processing 봇 → 유지r   rs  ru  r   r   r   r   r   r   rA   rv  rB   rE   rF   Nr   r!   r"   r%   r&   rw  )r4   ten_min_agor}  rv  r:   rK   rL   rM   r8   r9   r;   r<   s               r=   $test_recent_processing_not_recoveredzCTestCheckAndRecoverStuckBotsAW.test_recent_processing_not_recovered  s   ||HLL1Ib4IISSThiL;!OPQ33D9	yA~yAyyAF|F#H-==-====-===-==========r?   c                    dddddii}t         j                  |      }d}||k(  }|st        j                  d|fd||f      d	t	        j
                         v st        j                  |      rt        j                  |      nd	t        j                  |      d
z  }dd|iz  }t        t        j                  |            dx}}y)u   idle 봇 → 변경 없음r   r   r   z2026-01-01T00:00:00Zr   r   r   rA   rv  rB   rE   rF   N
r,   r{  r/   r0   rG   rH   rI   r1   r2   r3   r4   r}  rv  r:   rK   rL   rM   s          r=   test_idle_bot_not_affectedz9TestCheckAndRecoverStuckBotsAW.test_idle_bot_not_affected  s    F=S!TUV33D9	yA~yAyyAr?   c                    dddddii}t         j                  |      }d}||k(  }|st        j                  d|fd||f      d	t	        j
                         v st        j                  |      rt        j                  |      nd	t        j                  |      d
z  }dd|iz  }t        t        j                  |            dx}}y)u   잘못된 since 값은 스킵r   r   r   z
not-a-dater   r   r   rA   rv  rB   rE   rF   Nr  r  s          r=   test_invalid_since_skippedz9TestCheckAndRecoverStuckBotsAW.test_invalid_since_skipped  s~    L<!PQR33D9	yA~yAyyAr?   N)rQ   rR   rS   rT   r~  r  r  r  rU   r?   r=   rp  rp    s    Z8>r?   rp  c                       e Zd ZdZd Zd Zy)TestSaveBotActivityAWu2   activity-watcher.py의 save_bot_activity 테스트c                    |dz  }|t         _        ddddiii}t         j                  |      }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}}|j                  } |       }	|	sddt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |	      dz  }
t        t        j                  |
            dx}}	y)u   파일 저장 성공r   r   r   r   r   Trt   rv   r7   rB   rE   rF   Nr:  bot_filer<  )r,   r-   save_bot_activityr/   r0   rG   rH   rI   r1   r2   r3   rC  )r4   r5   r  r}  r7   r:   rK   rL   rM   r9   r;   s              r=   test_save_creates_filez,TestSaveBotActivityAW.test_save_creates_file  s    11'(F!345%%d+v~vvv         x   x             r?   c                    |dz  }|t         _        t         j                  di i       d}||z  }|j                  } |       }| }|sddt	        j
                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      t        j                  |      dz  }t        t        j                  |            dx}x}x}x}}y)u'   원자적 쓰기 후 .tmp 파일 없음r   r   zbot-activity.tmpzQassert not %(py7)s
{%(py7)s = %(py5)s
{%(py5)s = (%(py0)s / %(py2)s).exists
}()
}r5   )rC   rg   rF   r   N)r,   r-   r  rC  rG   rH   r/   rI   r1   r2   r3   )	r4   r5   r  rK   r9   r   r  r   r$  s	            r=   test_save_atomic_no_tmp_leftz2TestSaveBotActivityAW.test_save_atomic_no_tmp_left  s    11'
fb\*1;H11;199;9;;;;;;;;;;;H;;;H;;;1;;;9;;;;;;;;;;;r?   N)rQ   rR   rS   rT   r  r  rU   r?   r=   r  r    s    <!<r?   r  c                   "    e Zd ZdZd Zd Zd Zy)TestUpdateBotSinceu   since 필드 갱신 테스트c           	         |dz  dz  dz  }|j                   j                  d       |j                  t        j                  dddd	d
ii             |t
        _        t
        j                  d       t        |d      5 }t        j                  |      }ddd       d   d   d   }|j                  }d} ||      }|stdt        j                  |      t        j                  |      t        j                  |      t        j                  |      dz  }	t        t        j                  |	            dx}x}x}}y# 1 sw Y   xY w)u   since 필드 갱신 성공r   r   r   Tr   r   r   r   2026-03-01T00:00:00Zr   rNr   z2026-03zLassert %(py7)s
{%(py7)s = %(py3)s
{%(py3)s = %(py1)s.startswith
}(%(py5)s)
})r#   rD   rF   r   )r'   r(   r)   r*   r+   r,   r-   update_bot_sinceopenloadrj  r/   r1   r2   r3   )
r4   r5   r6   fr}  r8   r:   r   r  r   s
             r=   test_update_bot_since_successz0TestUpdateBotSince.test_update_bot_since_success  s   $x/(:=PP  &&t&4$$JJ6<R S	
  1
F##S) 	 Q99Q<D	  F|F#G,B,77B	B7	BBBBB,BBB7BBB	BBBBBBBBBB	  	 s   ?D==Ec           	      `   |dz  dz  dz  }|j                   j                  d       |j                  t        j                  dddd	d
ii             |t
        _        t
        j                  d       t        |d      5 }t        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!   존재하지 않는 봇은 무시r   r   r   Tr   r   r   r   r  r   dev99r  Nr   r   r!   r"   r%   r&   )r'   r(   r)   r*   r+   r,   r-   r  r  r  r/   r0   r1   r2   r3   )
r4   r5   r6   r  r}  r8   r9   r:   r;   r<   s
             r=   %test_update_bot_since_nonexistent_botz8TestUpdateBotSince.test_update_bot_since_nonexistent_bot  s    $x/(:=PP  &&t&4$$JJFE[)\ ]^_	
  1
G$#S) 	 Q99Q<D	 F|F#G,F0FF,0FFFFF,0FFFF,FFF0FFFFFFFF	  	 s   ?D$$D-c                    |dz  dz  d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)	u   파일이 없어도 생성r   r   r   r   zEassert not %(py4)s
{%(py4)s = %(py2)s
{%(py2)s = %(py0)s.exists
}()
}r6   r<  N)r,   r-   r  rC  rG   rH   r/   rI   r1   r2   r3   )r4   r5   r6   rK   r9   r   rM   s          r=   "test_update_bot_since_creates_filez5TestUpdateBotSince.test_update_bot_since_creates_file)  s    $x/(:=PP0
F#$++-+-----------$---$---+----------r?   N)rQ   rR   rS   rT   r  r  r  rU   r?   r=   r  r    s    'C,G.r?   r  __main__z-v)2rT   builtinsrG   _pytest.assertion.rewrite	assertionrewriter/   r*   r   systempfiler   r   r   pathlibr   unittest.mockr   r   pytestrequestsr   r   r   _ORIGINAL_WORKSPACE_ROOTmkdtemp_TEMP_WORKSPACEpathinsertrk   __file__r'   
__import__r,   popr   rW   r   r   r   r   r  r3  r8  rG  rp  r  r  rQ   mainrU   r?   r=   <module>r     s   A    	 
  2 2  *    ::>>*:; "(""$.

 !/

 .

?  3tH~,,334 5 "# #JJNN#T*#;BJJ %& %&Pk k\: :z   FU% U%p2 2$f fR% %>/ / n" n"b @< <(/. /.d zFKK4 ! r?   