
    i4                     *   d Z ddlZddlZddlZddlZddlZddlmZmZm	Z	 ddl
mZ ddlmZmZ ddlZ eej                   j#                  dd            Zedz  d	z  Zej(                  j+                  d
e      Zej(                  j/                  e      Zeej2                  d
<   ej4                  j7                  e        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jF                  e$dg       yy)u  bot-status-watchdog.py 테스트

테스트 항목:
1. parse_since_time() - ISO 8601 파싱
2. find_bot_process() - 프로세스 검색
3. should_transition_to_idle() - idle 전환 조건
4. check_and_recover_stuck_bots() - stuck 봇 복구
5. save_bot_activity() - 원자적 쓰기
    N)datetime	timedeltatimezone)Path)	MagicMockpatchWORKSPACE_ROOTz/home/jay/workspacescriptszbot-status-watchdog.pybot_status_watchdogc                   "    e Zd ZdZd Zd Zd Zy)TestParseSinceTimeu   parse_since_time() 테스트c                 @   t         j                  d      }|J |j                  dk(  sJ |j                  dk(  sJ |j                  dk(  sJ |j
                  dk(  sJ |j                  dk(  sJ |j                  dk(  sJ |j                  t        j                  k(  sJ y)	u(   UTC 형식 파싱 (2026-03-17T06:54:35Z)z2026-03-17T06:54:35ZN           6   #   )r   parse_since_timeyearmonthdayhourminutesecondtzinfor   utcselfresults     H/home/jay/workspace/.worktrees/task-2116-dev1/tests/test_bot_watchdog.pytest_parse_utc_formatz(TestParseSinceTime.test_parse_utc_format#   s    $556LM!!!{{d"""||q   zzR{{a}}"""}}"""}},,,    c                     t         j                  d      }|J |j                  dk(  sJ |j                  dk(  sJ |j                  dk(  sJ y)u-   KST 형식 파싱 (2026-03-17T15:54:35+09:00)z2026-03-17T15:54:35+09:00Nr   r   r   )r   r   r   r   r   r   s     r!   test_parse_kst_formatz(TestParseSinceTime.test_parse_kst_format/   sU    $556QR!!!{{d"""||q   zzRr#   c                     t         j                  d      J t         j                  d      J t         j                  d      J y)u'   잘못된 형식 파싱 → None 반환invalidN )r   r   )r   s    r!   test_parse_invalid_formatz,TestParseSinceTime.test_parse_invalid_format7   sG    "33I>FFF"33B7???"33D9AAAr#   N)__name__
__module____qualname____doc__r"   r%   r)    r#   r!   r   r       s    &
- Br#   r   c                   T    e Zd ZdZd Z ed      d        Z ed      d        Zd Zy)TestFindBotProcessu   find_bot_process() 테스트c                 T    t         j                  }d|v sJ d|v sJ d|v sJ d|v sJ y)u-   봇 이름 → workspace 패턴 매핑 확인anudev1dev2dev3N)r   BOT_WORKSPACE_PATTERNS)r   patternss     r!   !test_find_process_pattern_mappingz4TestFindBotProcess.test_find_process_pattern_mappingA   sG     '==   !!!!!!!!!r#   zsubprocess.runc                 l    t        ddd      |_        t        j                  d      }d|v sJ d|v sJ y)	u)   프로세스 발견 시 PID 목록 반환r   z12345
67890
r(   
returncodestdoutstderrr3   90  2	 Nr   return_valuer   find_bot_processr   mock_runpidss      r!   test_find_process_returns_pidsz1TestFindBotProcess.test_find_process_returns_pidsJ   s?     !*Q?OXZ ["33F;}}}}r#   c                 b    t        ddd      |_        t        j                  d      }|g k(  sJ y)u+   프로세스 없으면 빈 리스트 반환   r(   r:   r3   Nr@   rC   s      r!   test_find_process_no_processz/TestFindBotProcess.test_find_process_no_processS   s1     !*Qr" M"33F;rzzr#   c                 <    t         j                  d      }|g k(  sJ y)u$   알 수 없는 봇 → 빈 리스트unknownN)r   rB   )r   rE   s     r!   test_find_process_unknown_botz0TestFindBotProcess.test_find_process_unknown_bot[   s    "33I>rzzr#   N)	r*   r+   r,   r-   r8   r   rF   rI   rL   r.   r#   r!   r0   r0   >   sC    &"    r#   r0   c                      e Zd ZdZd Z ej                  edddg      d        Z ej                  edg        ej                  ed      d	               Z	 ej                  edg        ej                  edd
       ej                  ed      d                      Z
 ej                  edg        ej                  edd
       ej                  edd
      d                      Zy
)TestShouldTransitionToIdleu%   should_transition_to_idle() 테스트c                    t        j                  t        j                        t	        d      z
  }t        j                  t        dg       5  t        j                  d|d      \  }}ddd       du sJ d	k(  sJ y# 1 sw Y   xY w)
u   30분 미만 → 전환 안 함   minutesrB   rA   r3   g      4@NFnot_timeout)	r   nowr   r   r   r   objectr   should_transition_to_idle)r   
since_timeshould_idlereasons       r!   test_not_timeout_yetz/TestShouldTransitionToIdle.test_not_timeout_yetd   s    \\(,,/)B2GG
\\-/APRS 	j"5"O"OPVXbdh"iK	j e###&&&		j 	js   A>>BrB   r>   r?   rS   c                     t        j                  t        j                        t	        d      z
  }t
        j                  d|d      \  }}|du sJ d|v sJ d|v sJ y)	u,   프로세스 살아있음 → 전환 안 함r   rQ   r3        A@Fstill_running12345Nr   rU   r   r   r   r   rW   )r   	mock_findrX   rY   rZ   s        r!   test_process_still_runningz5TestShouldTransitionToIdle.test_process_still_runningn   se     \\(,,/)B2GG
1KKFT^`deVe###&(((&   r#   find_recent_done_filec                     t        j                  t        j                        t	        d      z
  }t
        dz  dz  dz  |_        t        j                  d|d      \  }}|du sJ d	|v sJ d
|v sJ y)u   .done 파일 있음 → 전환r   rQ   memoryeventsztask-123.1.dev1.doner3   r]   T	completedz.doneN	r   rU   r   r   r   
_WORKSPACErA   r   rW   )r   	mock_donera   rX   rY   rZ   s         r!   test_done_file_existsz0TestShouldTransitionToIdle.test_done_file_existsy   s}     \\(,,/)B2GG
!+h!6!ADZ!Z	1KKFT^`deVd"""f$$$&   r#   Nfind_recent_reportc                     t        j                  t        j                        t	        d      z
  }t
        dz  dz  dz  |_        t        j                  d|d      \  }}|du sJ d	|v sJ d
|v sJ y)u"   보고서 파일 있음 → 전환r   rQ   re   reportsztask-123.1.mdr3   r]   Trg   u	   보고서Nrh   r   mock_reportrj   ra   rX   rY   rZ   s          r!   test_report_existsz-TestShouldTransitionToIdle.test_report_exists   s|    
 \\(,,/)B2GG
#-#89#D#V 1KKFT^`deVd"""f$$$f$$$r#   c                     t        j                  t        j                        t	        d      z
  }t
        j                  d|d      \  }}|du sJ d|v sJ y)u-   프로세스 없음 + 30분 초과 → 전환r   rQ   r3   r]   TtimeoutNr`   ro   s          r!   test_timeout_no_processz2TestShouldTransitionToIdle.test_timeout_no_process   sW    
 \\(,,/)B2GG
1KKFT^`deVd"""F"""r#   )r*   r+   r,   r-   r[   r   rV   r   rb   rk   rq   rt   r.   r#   r!   rN   rN   a   s$   /' U\\%'9PUW! X! U\\%'9KU\\%'>?	! @ L	! U\\%'9KU\\%'>TRU\\%';<	% = S L	% U\\%'9KU\\%'>TRU\\%';$O# P S L#r#   rN   c                   F    e Zd ZdZej
                  d        Zd Zd Zd Z	y)TestCheckAndRecoverStuckBotsu(   check_and_recover_stuck_bots() 테스트c                    t        j                  t        j                        t	        d      z
  }|j                  d      }t        j                  t        j                        t	        d      z
  }|j                  d      }dd|dd|dd|dd	i}|d
z  }|j                  t        j                  |d      d       ||fS )u   임시 bot-activity.json 생성(   rQ   %Y-%m-%dT%H:%M:%SZ
   bots
processingstatussinceidle)r3   r4   r5   bot-activity.json   indentutf-8encoding)	r   rU   r   r   r   strftime
write_textjsondumps)r   tmp_pathold_timeold_time_strrecent_timerecent_time_strdatabot_activity_files           r!   temp_bot_activityz.TestCheckAndRecoverStuckBots.temp_bot_activity   s     <<-	"0EE(()=> ll8<<09R3HH%../CD #/,G#//J#)OD
 %'::$$TZZQ%?'$R~r#   c                    |\  }}|j                  t        d|dz         |j                  t        d|dz         |j                  t        d|dz         |j                  t        d|dz         t        j                  t        d	g 
      5  t        j                  t        dd
      5  t        j                  t        dd
      5  t        j	                         }ddd       ddd       ddd       dk(  sJ t        j                  |dz  j                  d            }|d   d   d   dk(  sJ |d   d   d   dk(  sJ y# 1 sw Y   ixY w# 1 sw Y   mxY w# 1 sw Y   qxY w)u   stuck 봇 복구 테스트BOT_ACTIVITY_FILEr   
EVENTS_DIRrf   REPORTS_DIRrn   WATCHDOG_LOGbot-watchdog.logrB   rS   rc   Nrl   rH   r   r   r{   r3   r~   r   r4   r|   )setattrr   r   rV   check_and_recover_stuck_botsr   loads	read_text)r   r   monkeypatchr   r   	recoveredupdated_datas          r!   test_recover_stuck_botsz4TestCheckAndRecoverStuckBots.test_recover_stuck_bots   sq   *$ 	/1DhQdFde/x(?RS/9@TU/L^A^_ \\-/APRS 	S13JY]^ S\\"57KZ^_ S 3 P P RISS	S A~~ zz8.A#A"L"LV]"L"^_F#F+H5???F#F+H5EEES SS S	S 	Ss<   
E'EEE!EE
EE	EE"c                 ^   |j                  t        d|dz         |j                  t        d|dz         |j                  t        d|dz         |j                  t        d|dz         t        j                  t        j
                        t        d	
      z
  }|j                  d      }dd|dd|ddi}|dz  j                  t        j                  |d      d       t        j                  t        dg       5  t        j                         }ddd       dk(  sJ y# 1 sw Y   xY w)u   stuck 봇이 없는 경우r   r   r   rf   r   rn   r   r   rz   rQ   ry   r{   r|   r}   r   )r3   r4   r   r   r   r   rB   rS   Nr   )r   r   r   rU   r   r   r   r   r   r   r   r   rV   r   )r   r   r   r   r   r   r   s          r!   test_no_stuck_botsz/TestCheckAndRecoverStuckBots.test_no_stuck_bots   s"   /1DhQdFde/x(?RS/9@TU/L^A^_ll8<<09R3HH%../CD #//J#)OD
 
'	'33DJJtA4NY`3a\\-/APRS 	K+HHJI	K A~~	K 	Ks   >D##D,c                    |j                  t        d|dz         |j                  t        d|dz         |j                  t        d|dz         |j                  t        d|dz         t        j                  t        j
                        t        d	
      z
  }|j                  d      }ddd|dii}|dz  j                  t        j                  |d      d       t        j                  t        ddg      5  t        j                         }ddd       dk(  sJ t        j                  |dz  j                  d            }|d   d   d   dk(  sJ y# 1 sw Y   IxY w)u0   프로세스 살아있으면 idle 전환 안 함r   r   r   rf   r   rn   r   r   rx   rQ   ry   r{   r3   r|   r}   r   r   r   r   rB   r>   rS   Nr   r~   )r   r   r   rU   r   r   r   r   r   r   r   r   rV   r   r   r   )r   r   r   r   r   r   r   r   s           r!   test_skip_if_process_runningz9TestCheckAndRecoverStuckBots.test_skip_if_process_running   sZ   /1DhQdFde/x(?RS/9@TU/L^A^_ <<-	"0EE(()=>L<!PQR	'	'33DJJtA4NY`3a \\-/AQVPWX 	K+HHJI	K A~~zz8.A#A"L"LV]"L"^_F#F+H5EEE	K 	Ks   ;EE!N)
r*   r+   r,   r-   pytestfixturer   r   r   r   r.   r#   r!   rv   rv      s,    2^^ .F00Fr#   rv   c                       e Zd ZdZd Zy)TestSaveBotActivityu   save_bot_activity() 테스트c                    |j                  t        d|dz         dddddii}t        j                  |      }|du sJ |dz  }|j                         sJ t	        j
                  |j                  d	
            }||k(  sJ y)u   원자적 쓰기 테스트r   r   r{   r3   r   z2026-03-17T07:00:00Zr}   Tr   r   N)r   r   save_bot_activityexistsr   r   r   )r   r   r   r   r    r   loadeds          r!   test_atomic_writez%TestSaveBotActivity.test_atomic_write	  s    /1DhQdFdeF=S!TUV$66t<~~ %':: ''))) -777IJ~~r#   N)r*   r+   r,   r-   r   r.   r#   r!   r   r     s
    'r#   r   c                       e Zd ZdZd Zy)TestWatchdogOnceu   워치독 1회 실행 테스트c                    |j                  t        d|dz         |j                  t        d|dz         |j                  t        d|dz         |j                  t        d|dz         t        j                  t        j
                        t        d	
      z
  }|j                  d      }ddd|dii}|dz  j                  t        j                  |d      d       t        j                  t        dg       5  t        j                  t        dd      5  t        j                  t        dd      5  t        j                          ddd       ddd       ddd       |dz  }|j                         sJ t        j                  |dz  j!                  d            }|d   d   d   dk(  sJ y# 1 sw Y   ixY w# 1 sw Y   mxY w# 1 sw Y   qxY w)u   1회 실행 테스트r   r   r   rf   r   rn   r   r   rx   rQ   ry   r{   r3   r|   r}   r   r   r   r   rB   rS   rc   Nrl   r~   r   )r   r   r   rU   r   r   r   r   r   r   r   r   rV   run_oncer   r   r   )r   r   r   r   r   r   log_filer   s           r!   test_run_oncezTestWatchdogOnce.test_run_once  s    	/1DhQdFde/x(?RS/9@TU/L^A^_ <<-	"0EE(()=>L<!PQR	'	'33DJJtA4NY`3a \\-/APRS 	313JY]^ 3\\"57KZ^_ 3'00233	3 00    zz8.A#A"L"LV]"L"^_F#F+H5???3 33 3	3 	3s<   :G	F=4F1	F=G	1F:6F==G	G		GN)r*   r+   r,   r-   r   r.   r#   r!   r   r     s    )@r#   r   __main__z-v)%r-   importlib.util	importlibr   ossystempfiler   r   r   pathlibr   unittest.mockr   r   r   environgetri   SCRIPT_PATHutilspec_from_file_locationspecmodule_from_specr   modulesloaderexec_moduler   r0   rN   rv   r   r   r*   main__file__r.   r#   r!   <module>r      s     	 
  2 2  *  "**..!13HIJ
9$'??~~--.C[Qnn55d; %8! "   + ,B B<   F=# =#@bF bFJ *@ @D zFKK4 ! r#   