
    Ris                     6   d 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j&                         Zeej                   d<   dej                   d<   d	ej                   d
<   ej*                  j-                  d e e	e      j2                  j2                                ed      Zeej                   j9                  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)e*d&k(  r ejV                  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(  sJ |d   d   d   d
k(  sJ 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   r   N)parentmkdir
write_textjsondumpsawBOT_ACTIVITY_FILEload_bot_activityselftmp_pathbot_activity_fileresults       T/home/jay/workspace/.worktrees/task-2117-dev1/scripts/tests/test_activity_watcher.pytest_load_valid_jsonz(TestLoadBotActivity.test_load_valid_json'   s    $x/(:=PP  &&t&4$$JJ+1<R S+7BX Y		
  1%%'f~f%h/6999f~f%h/<???    c                 Z    |dz  t         _        t         j                         }|di ik(  sJ y)u#   파일이 없으면 빈 dict 반환znonexistent.jsonr   N)r$   r%   r&   r(   r)   r+   s      r,   test_load_missing_filez*TestLoadBotActivity.test_load_missing_file<   s0    '*<<%%'&"%%%r.   c                     |dz  }|j                  d       |t        _        t        j                         }|di ik(  sJ y)u&   JSON 파싱 에러 시 빈 dict 반환r   zinvalid json {r   N)r!   r$   r%   r&   r'   s       r,   test_load_invalid_jsonz*TestLoadBotActivity.test_load_invalid_jsonB   sE    $'::$$%560%%'&"%%%r.   N)__name__
__module____qualname____doc__r-   r1   r3    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           	         |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(  sJ 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   Nr    r!   r   r"   r#   r$   
EVENTS_DIRr   objectstrfind_done_file)r(   r)   
events_dir	done_file
timer_filer+   s         r,   test_find_done_file_team_basedz/TestFindDoneFile.test_find_done_file_team_basedO   s    (83
&!22	T" (+==
t<JJ$'2&/'		
 #\\".H> 	/&&v.F	/ """	/ 	/s   0CC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       J y# 1 sw Y   xY w)!   done 파일 없으면 None 반환r   r   Tr   r>   r?   rA   
task-456.1rC   rD   rE   r	   r   Nr    r   r!   r"   r#   r$   rH   r   rI   rJ   rK   r(   r)   rL   rN   r+   s        r,   test_find_done_file_not_existsz/TestFindDoneFile.test_find_done_file_not_existsl   s    (83
& (+==
t<JJ$'2&/'		
 #\\".H> 	/&&v.F	/ ~~	/ 	/s   B==C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       J y# 1 sw Y   xY w)uC   .done.notified, .done.acked 등이 있으면 해당 .done은 무시r   r   Tr   r<   r=   task-123.1.done.notified r>   r?   rA   rB   rC   rD   rE   r	   r   NrG   rT   s        r,   0test_find_done_file_ignores_processed_extensionszATestFindDoneFile.test_find_done_file_ignores_processed_extensions   s    (83
&	'	'33D9	0	0<<R@(+==
t<JJ$'2&/'		
 #\\".H> 	/&&v.F	/ ~~	/ 	/   C%%C.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       J y# 1 sw Y   xY w)u   .done.acked 존재 시 무시r   r   Tr   r<   r=   ztask-123.1.done.ackedrX   r>   r?   rA   rB   rC   rD   rE   r	   r   NrG   rT   s        r,   !test_find_done_file_ignores_ackedz2TestFindDoneFile.test_find_done_file_ignores_acked   s    (83
&	'	'33D9	-	-99"=(+==
t<JJ$'2&/'		
 #\\".H> 	/&&v.F	/ ~~	/ 	/rZ   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       J y# 1 sw Y   xY w)u&   활성 태스크 없으면 None 반환r   r   Tr   r<   r=   r>   r?   rA   r	   r   NrG   rT   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	/ ~~	/ 	/s   )CCc                     |dz  dz  }|j                  d       |t        _        t        j                  d      }|J y)u3   anu는 BOT_TEAM_MAP에서 None이므로 항상 Noner   r   Tr   anuN)r    r$   rH   rK   r(   r)   rL   r+   s       r,   $test_find_done_file_anu_returns_nonez5TestFindDoneFile.test_find_done_file_anu_returns_none   sC    (83
&"""5)~~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(  sJ d| d       |j                           y# 1 sw Y   -xY w)u   dev4~dev8 팀 매칭 지원)               r   r   Tr?   ztask-z	00.1.doner=   r>   rA   z00.1devz-teamrD   rE   r	   Nu    팀 매칭 실패)r    r!   r   r"   r#   r$   rH   r   rI   rJ   rK   unlink)r(   r)   dev_numrL   rM   rN   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#gY6H(II& 9	,< <s   DD	N)r4   r5   r6   r7   rO   rU   rY   r\   r^   rb   rl   r8   r.   r,   r:   r:   L   s(    :#:688"r.   r:   c                   (    e Zd ZdZd Zd Zd Zd Zy)TestGetActiveTaskForTeamu)   get_active_task_for_team 함수 테스트c           	      V   |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(  sJ y# 1 sw Y   xY w)u)   팀에 매칭되는 활성 task_id 반환r   r>   Tr?   rA   	dev1-teamrD   rE   rC   )
task-100.1z
task-200.1r	   r   Nrq   
r   r    r!   r"   r#   r   rI   r$   rJ   get_active_task_for_teamr(   r)   rN   r+   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 %%%	9 	9s   9BB(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       J y# 1 sw Y   xY w)	u    매칭 팀 없으면 None 반환r   r>   Tr?   rA   r	   dev9Nrr   rt   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 ~~	9 	9s   0BBc           	      H   |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       J y# 1 sw Y   xY w)u%   status != running 태스크는 무시r   r>   Tr?   rA   rq   rp   donerE   r	   r   Nrr   rt   s       r,   test_ignores_non_running_tasksz7TestGetActiveTaskForTeam.test_ignores_non_running_tasks  s    (+==
t<JJ$+&P	
 \\".H> 	9008F	9 ~~	9 	9s   5BB!c                     t        j                  t        dt        |            5  t        j	                  d      }ddd       J y# 1 sw Y   xY w)u&   task-timers.json 없으면 None 반환r	   r   N)r   rI   r$   rJ   rs   r0   s      r,   #test_returns_none_when_file_missingz<TestGetActiveTaskForTeam.test_returns_none_when_file_missing/  sH    \\".H> 	9008F	9 ~~	9 	9s   AAN)r4   r5   r6   r7   ru   rx   r{   r}   r8   r.   r,   rn   rn      s    3&(	&r.   rn   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 sJ y)u,   .done.notified 마커 파일 존재 시 Truer   r   Tr   rW   rB   Nr    touchr$   rH   check_already_notified)r(   r)   rL   notified_filer+   s        r,   #test_already_notified_marker_existsz<TestCheckAlreadyNotified.test_already_notified_marker_exists:  s\    (83
&"%??"**<8~~r.   c                     |dz  dz  }|j                  d       |t        _        t        j                  d      }|du sJ y)u,   .done.notified 마커 파일 없으면 Falser   r   Tr   rB   FN)r    r$   rH   r   ra   s       r,    test_not_notified_marker_missingz9TestCheckAlreadyNotified.test_not_notified_marker_missingE  sE    (83
&"**<8r.   c                     |dz  dz  }|j                  d       |dz  j                          |t        _        t        j	                  d      }|du sJ y)	u)   다른 task_id의 마커는 영향 없음r   r   Tr   ztask-999.1.done.notifiedrB   FNr   ra   s       r,   #test_different_task_id_not_confusedz<TestCheckAlreadyNotified.test_different_task_id_not_confusedN  sW    (83
&	0	0779"**<8r.   N)r4   r5   r6   r7   r   r   r   r8   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 sJ j                          |j                  d   }|j                  di       }|j                  d      dk(  sJ |j                  d      dk(  sJ y	# 1 sw Y   uxY w# 1 sw Y   yxY w)u!   알림 전송 성공 (200 응답)   
os.environr   
test-tokenrequests.postreturn_valuetest messagechat_idNT   r"   text)	r   status_coder   dictr$   send_telegram_notificationassert_called_once	call_argsget)r(   	mock_resp	mock_postr+   call_kwargspayloads         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 ~~$$&))!,//&"-{{6"n444{{9%222R R	R 	Rs"   CCCC	CCc                    t         j                  j                         D ci c]  \  }}|dk7  s|| }}}t        j                  d|d      5  t
        j                  dd      }ddd       du sJ yc c}}w # 1 sw Y   xY w)	u$   ANU_BOT_TOKEN 없으면 False 반환r   r   T)clearr   r   NF)osenvironitemsr   r   r$   r   )r(   kvenvr+   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 L	N 	Ns   A6A6A<<Bc                 
   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 sJ 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   NF)r   r   headersr   r   r$   r   )r(   r   r+   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 R R	R 	Rs"   A9A-A9-A6	2A99Bc                    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 sJ j                  dk(  sJ y# 1 sw Y   1xY w# 1 sw Y   5xY w# 1 sw Y   9xY w)u   429 응답 시 재시도i  zRetry-After1r   r   r   r   r   side_effect
time.sleepr   r   NT   )r   r   r   r   r   r$   r   
call_count)r(   mock_429mock_200r   r+   s        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
 ~~##q(((	V VV V	V 	Vs<   B?B3 B'7B3?B?'B0,B33B<	8B??C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 sJ 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   NF)r   r   real_requestsRequestExceptionr$   r   )r(   r+   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s:   !BBA<B%B<BBB	BB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(  sJ y	# 1 sw Y   7x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   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)R///	I I	I 	Is"   BBBB	
BB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)r(   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)r4   r5   r6   r7   r   r   r   r   r   r   r   r8   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                 j    dD ].  }t         j                  j                  |      |k(  r&J | d        y)u   dev1~dev3 매핑 확인)r   r   dev3    매핑 없음Nr$   BOT_TEAM_MAPr   r(   ri   s     r,   test_dev1_to_dev3_mappedz'TestBotTeamMap.test_dev1_to_dev3_mapped  s<    + 	KC??&&s+s2Jse>4JJ2	Kr.   c                 j    dD ].  }t         j                  j                  |      |k(  r&J | d        y)u   dev4~dev8 매핑 확인)dev4dev5dev6dev7dev8r   Nr   r   s     r,   test_dev4_to_dev8_mappedz'TestBotTeamMap.test_dev4_to_dev8_mapped  s<    ; 	KC??&&s+s2Jse>4JJ2	Kr.   c                 F    t         j                  j                  d      J y)u   anu는 None으로 매핑r`   Nr   )r(   s    r,   test_anu_mapped_to_nonez&TestBotTeamMap.test_anu_mapped_to_none  s    ""5)111r.   N)r4   r5   r6   r7   r   r   r   r8   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(  sJ y)u3   idle → idle (변화 없음) 시 알림 안 보냄r   r   r   Tr   r   r   r   r   r   r   r`   r   r   Nr   r    r!   r"   r#   r$   r%   r&   r   r   copyappendlen)r(   r)   r*   prev_activityprev_statusesbot_namebot_datacurr_statusesnotifications_sentcurr_statusprev_statuss              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 %&!+++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   r<   r=   r>   r?   rA   rB   rC   rD   rE   done-protocol.logr   r   r   r	   r   r`   r   N)r    r!   r   r"   r#   r$   rH   DONE_PROTOCOL_LOGr   rI   rJ   r   r   r   rK   stemr   r   r   )r(   r)   rL   rM   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(  sJ y)u    아누(anu) 상태 변화 무시r`   r   r   r   N)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 %&!+++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
       J y
# 1 sw Y   xY w)rQ   r   r   Tr   r>   r?   rA   r	   r   NrS   rT   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	/~~	/ 	/s   B88CN)r4   r5   r6   r7   r   r   r   r   r8   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(  sJ y)u3   시작 시 초기 상태 로드로 오알림 방지r   r   r   Tr   r   r   r   r   r   r   r`   r   r   Nr   )r(   r)   r*   r   r   r   r   r   transitionsr   r   s              r,   *test_initial_state_prevents_false_positivez;TestInitialState.test_initial_state_prevents_false_positive0  s8   $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$$$r.   N)r4   r5   r6   r7   r   r8   r.   r,   r   r   -  s
    (%r.   r   c                       e Zd ZdZd Zy)TestLogProtocolu   로그 기록 테스트c                     |dz  }|t         _        t         j                  d       |j                         sJ |j	                         }d|v sJ d|v sJ y)u   로그 파일에 기록r   r   z[activity-watcher]N)r$   r   log_protocolexists	read_text)r(   r)   log_filecontents       r,   test_log_protocol_writesz(TestLogProtocol.test_log_protocol_writesO  s[    11'
'   $$&(((#w...r.   N)r4   r5   r6   r7   r   r8   r.   r,   r   r   L  s
    !
/r.   r   c                   .    e Zd ZdZd Zd Zd Zd Zd Zy)TestExtractReportSummaryu!   보고서 요약 추출 테스트c                    |dz  dz  }|j                  d       |dz  }d}|j                  |       t        |      t        _        t        j                  dd      }d|v sJ d|v sJ d	|v sJ d
|v sJ d|v sJ d|v sJ y)u)   SCQA 형식 보고서에서 S와 A 추출r   reportsTr   ztask-123.1.mdu@  # task-123.1 완료 보고서

## SCQA

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

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

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

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

## 구현 상세
rB   r   S:u
   500 에러zA:u   로그 분석Nr    r!   rJ   r$   r	   extract_report_summaryr(   r)   reports_dirreport_filereport_contentr+   s         r,   test_extract_with_scqaz/TestExtractReportSummary.test_extract_with_scqa_  s    )I5$'!O3 	~.M**<@v%%%v~~v%%%v~~&(((r.   c                     t        |      t        _        t        j                  dd      }d|v sJ d|v sJ t        |      |v sd|v sJ yy)u9   보고서 파일 없을 때 폴백 - 절대 경로 사용z
task-404.1r   u
   보고서:z/home/jay/workspaceN)rJ   r$   r	   r  r0   s      r,   test_extract_without_reportz4TestExtractReportSummary.test_extract_without_report  s\    M**<@v%%%v%%%8}&*?6*III*I&r.   c                     |dz  dz  }|j                  d       |dz  }d}|j                  |       t        |      t        _        t        j                  dd      }d|v sJ d|v sJ y	)
u:   SCQA 형식이 아닌 보고서도 크래시 없이 처리r   r  Tr   ztask-456.1.mduI   # task-456.1 완료

## 작업 내용
- 버그 수정
- 테스트 추가
rR   r   Nr  r  s         r,   test_extract_non_scqa_reportz5TestExtractReportSummary.test_extract_non_scqa_report  s}    )I5$'!O3 	~.M**<@v%%%r.   c                     |dz  dz  }|j                  d       |dz  }d}|j                  |       t        |      t        _        t        j                  dd      }d	|v sJ y
)u   수정 파일 수 포함r   r  Tr   ztask-789.1.mduj   # task-789.1 완료

## SCQA

**S**: 문제 발생

**A**: 해결 완료

## 수정 내역
- 수정: 5건
z
task-789.1r   u   수정: 5건Nr  r  s         r,   test_extract_with_files_countz6TestExtractReportSummary.test_extract_with_files_count  so    )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  sJ yc c}w )u!   긴 텍스트는 150자로 잘림r   r  Tr   ztask-long.1.mdAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAu&   # task-long.1 완료

## SCQA

**S**: u   

**A**: 해결
ztask-long.1r   
r  r      N)	r    r!   rJ   r$   r	   r  split
startswithr   )
r(   r)   r	  r
  	long_textr  r+   lineslines_lines
             r,    test_extract_long_text_truncatedz9TestExtractReportSummary.test_extract_long_text_truncated  s    )I5$'!$44	 	{ 	 	~.M**=&AT"#(B4DOOD,A$B1E6{c!!! Cs   8B*B*N)	r4   r5   r6   r7   r  r  r  r  r  r8   r.   r,   r  r  \  s     +)@J ((0"r.   r  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(  sJ |d   d   d	   d
k(  sJ y)u+   40분 경과 processing 봇 → idle 전환(   minutes%Y-%m-%dT%H:%M:%SZr   r   r   r   r   r   r   Nr   nowr   utcr   strftimer$   check_and_recover_stuck_bots)r(   forty_min_agodata	recovereds       r,   test_stuck_bot_recoveredz7TestCheckAndRecoverStuckBotsAW.test_stuck_bot_recovered  sv    !hll3i6KKUUVjkL=!QRS33D9	A~~F|F#H-777r.   c                     t        j                  t        j                        t	        d      z
  j                  d      }ddd|dii}t        j                  |      }|dk(  sJ |d   d   d	   dk(  sJ y
)u&   10분 경과 processing 봇 → 유지r   r"  r$  r   r   r   r   r   r   Nr%  )r(   ten_min_agor+  r,  s       r,   $test_recent_processing_not_recoveredzCTestCheckAndRecoverStuckBotsAW.test_recent_processing_not_recovered  sv    ||HLL1Ib4IISSThiL;!OPQ33D9	A~~F|F#H-===r.   c                 N    dddddii}t         j                  |      }|dk(  sJ y)u   idle 봇 → 변경 없음r   r   r   z2026-01-01T00:00:00Zr   r   Nr$   r)  r(   r+  r,  s      r,   test_idle_bot_not_affectedz9TestCheckAndRecoverStuckBotsAW.test_idle_bot_not_affected  s3    F=S!TUV33D9	A~~r.   c                 N    dddddii}t         j                  |      }|dk(  sJ y)u   잘못된 since 값은 스킵r   r   r   z
not-a-dater   r   Nr2  r3  s      r,   test_invalid_since_skippedz9TestCheckAndRecoverStuckBotsAW.test_invalid_since_skipped  s2    L<!PQR33D9	A~~r.   N)r4   r5   r6   r7   r-  r0  r4  r6  r8   r.   r,   r  r    s    Z8>r.   r  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 sJ |j                         sJ y)u   파일 저장 성공r   r   r   r   r   TNr$   r%   save_bot_activityr   )r(   r)   bot_filer+  r+   s        r,   test_save_creates_filez,TestSaveBotActivityAW.test_save_creates_file  sU    11'(F!345%%d+~~   r.   c                 |    |dz  }|t         _        t         j                  di i       |dz  j                         rJ y)u'   원자적 쓰기 후 .tmp 파일 없음r   r   zbot-activity.tmpNr:  )r(   r)   r<  s      r,   test_save_atomic_no_tmp_leftz2TestSaveBotActivityAW.test_save_atomic_no_tmp_left  sB    11'
fb\*1199;;;;r.   N)r4   r5   r6   r7   r=  r?  r8   r.   r,   r8  r8    s    <!<r.   r8  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      sJ 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-03)r   r    r!   r"   r#   r$   r%   update_bot_sinceopenloadr  r(   r)   r*   fr+  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,77	BBB	  	 s   ?B::Cc           	      t   |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(  sJ y# 1 sw Y   xY w)u!   존재하지 않는 봇은 무시r   r   r   Tr   r   r   r   rC  r   dev99rD  Nr   )
r   r    r!   r"   r#   r$   r%   rE  rF  rG  rH  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,0FFFF	  	 s   ?B..B7c                 ~    |dz  dz  dz  }|t         _        t         j                  d       |j                         rJ y)u   파일이 없어도 생성r   r   r   r   N)r$   r%   rE  r   )r(   r)   r*   s      r,   "test_update_bot_since_creates_filez5TestUpdateBotSince.test_update_bot_since_creates_file)  sC    $x/(:=PP0
F#$++----r.   N)r4   r5   r6   r7   rJ  rM  rO  r8   r.   r,   rA  rA    s    'C,G.r.   rA  __main__z-v),r7   r"   r   systempfiler   r   r   pathlibr   unittest.mockr   r   pytestrequestsr   r   r   _ORIGINAL_WORKSPACE_ROOTmkdtemp_TEMP_WORKSPACEpathinsertrJ   __file__r   
__import__r$   popr   r:   rn   r   r   r   r   r   r   r  r  r8  rA  r4   mainr8   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.   