
    Rii5                        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 ddlZ ej                         Zeej                  d<   ej                   j#                  d e e	e      j(                  j(                                e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j>                  edg       yy)u*   bot-status-watchdog.py 테스트 스위트    N)datetime	timedeltatimezone)Path)patchWORKSPACE_ROOTzbot-status-watchdogc                   (    e Zd ZdZd Zd Zd Zd Zy)TestParseSinceTimeu   since 시간 파싱 테스트c                     t         j                  d      }|J |j                  t        j                  k(  sJ |j
                  dk(  sJ y)u   UTC Z 포맷 파싱z2026-03-17T06:00:00ZN   )watchdogparse_since_timetzinfor   utchourselfresults     W/home/jay/workspace/.worktrees/task-2117-dev1/scripts/tests/test_bot_status_watchdog.pytest_parse_utc_z_formatz*TestParseSinceTime.test_parse_utc_z_format   sG    **+AB!!!}},,,{{a    c                 6    t         j                  d      }|J y)u   ISO 오프셋 포맷 파싱z2026-03-17T06:00:00+09:00Nr   r   r   s     r   test_parse_iso_with_offsetz-TestParseSinceTime.test_parse_iso_with_offset    s    **+FG!!!r   c                 6    t         j                  d      }|J y)u   잘못된 형식은 None 반환z
not-a-dateNr   r   s     r   test_parse_invalid_returns_nonez2TestParseSinceTime.test_parse_invalid_returns_none%   s    **<8~~r   c                 6    t         j                  d      }|J y)u   빈 문자열은 None 반환 Nr   r   s     r   test_parse_empty_returns_nonez0TestParseSinceTime.test_parse_empty_returns_none*   s    **2.~~r   N)__name__
__module____qualname____doc__r   r   r   r    r   r   r
   r
      s    ' "

r   r
   c                   "    e Zd ZdZd Zd Zd Zy)TestLoadBotActivityu"   bot-activity.json 로드 테스트c           	          |dz  }|j                  t        j                  dddddii             |t        _        t        j                         }|d   d   d   dk(  sJ y)	u   유효한 JSON 로드bot-activity.jsonbotsdev1idle2026-03-17T00:00:00Zstatussincer.   N)
write_textjsondumpsr   BOT_ACTIVITY_FILEload_bot_activityr   tmp_pathbot_filer   s       r   test_load_valid_jsonz(TestLoadBotActivity.test_load_valid_json3   sf    11DJJFUk9l0m'nop%-"++-f~f%h/6999r   c                 Z    |dz  t         _        t         j                         }|di ik(  sJ y)u    파일 없으면 빈 dict 반환znonexistent.jsonr)   N)r   r3   r4   )r   r6   r   s      r   $test_load_missing_file_returns_emptyz8TestLoadBotActivity.test_load_missing_file_returns_empty;   s0    %-0B%B"++-&"%%%r   c                     |dz  }|j                  d       |t        _        t        j                         }|di ik(  sJ y)u!   잘못된 JSON은 빈 dict 반환r(   z	{invalid}r)   N)r0   r   r3   r4   r5   s       r   $test_load_invalid_json_returns_emptyz8TestLoadBotActivity.test_load_invalid_json_returns_emptyA   sC    11K(%-"++-&"%%%r   N)r    r!   r"   r#   r8   r:   r<   r$   r   r   r&   r&   0   s    ,:&&r   r&   c                       e Zd ZdZd Zd Zy)TestSaveBotActivityu"   bot-activity.json 저장 테스트c                     |dz  }|t         _        ddddiii}t         j                  |      }|du sJ |j                         sJ t	        j
                  |j                               }|d   d   d   dk(  sJ y)u   파일 생성 확인r(   r)   r*   r.   r+   TN)r   r3   save_bot_activityexistsr1   loads	read_text)r   r6   r7   datar   saveds         r   test_save_creates_filez*TestSaveBotActivity.test_save_creates_fileM   s    11%-"(F!345++D1~~   

8--/0V}V$X.&888r   c                     |dz  }|t         _        di i}t         j                  |       |dz  j                         rJ y)u8   원자적 쓰기 - tmp 파일이 최종 파일로 대체r(   r)   zbot-activity.tmpN)r   r3   r@   rA   )r   r6   r7   rD   s       r   test_save_atomic_writez*TestSaveBotActivity.test_save_atomic_writeX   sG    11%-"|""4(1199;;;;r   N)r    r!   r"   r#   rF   rH   r$   r   r   r>   r>   J   s    ,	9<r   r>   c                   .    e Zd ZdZd Zd Zd Zd Zd Zy)TestShouldTransitionToIdleu   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분 미만 → 전환 안 함
   minutesfind_bot_processreturn_valuer*   g      $@NFnot_timeout	r   nowr   r   r   r   objectr   should_transition_to_idler   r6   
since_timeshouldreasons        r   test_not_timeout_returns_falsez9TestShouldTransitionToIdle.test_not_timeout_returns_falsee   s{    \\(,,/)B2GG
\\($6RH 	Z%??
TXYNFF	Z&&&	Z 	Zs   A>>Bc                     t        j                  t        j                        t	        d      z
  }t        j                  t        ddg      5  t        j                  d|d      \  }}ddd       d	u sJ d
v sJ d|v sJ y# 1 sw Y   xY w)u?   30분 초과지만 프로세스 살아있음 → 전환 안 함(   rM   rO   90  rP   r*         D@NFstill_running12345rS   rW   s        r    test_process_alive_returns_falsez;TestShouldTransitionToIdle.test_process_alive_returns_falsem   s    \\(,,/)B2GG
\\($6eWM 	Z%??
TXYNFF	Z&(((&   		Z 	Zs   BBc                    t        j                  t        j                        t	        d      z
  }|dz  }|j                          t        j                  t        dg       5  t        j                  t        d|      5  t        j                  d|d      \  }}d	d	d	       d	d	d	       d
u sJ dv sJ d|v sJ y	# 1 sw Y   $xY w# 1 sw Y   (xY w)u4   프로세스 없음 + .done 파일 있음 → 전환r]   rM   ztask-123.donerO   rP   find_recent_done_filer*   r_   NT	completedz.done
r   rT   r   r   r   touchr   rU   r   rV   )r   r6   rX   	fake_donerY   rZ   s         r   "test_done_file_exists_returns_truez=TestShouldTransitionToIdle.test_done_file_exists_returns_truev   s    \\(,,/)B2GG
.	LL#5BG	ZLL#:S	Z &??
TXYNFF		Z 	Z
 ~~f$$$&   	Z 	Z 	Z 	Zs$   "C	?B=C	=C	C		Cc                    t        j                  t        j                        t	        d      z
  }|dz  }|j                          t        j                  t        dg       5  t        j                  t        dd      5  t        j                  t        d|      5  t        j                  d	|d
      \  }}ddd       ddd       ddd       du sJ dv sJ d|v sJ y# 1 sw Y   ,xY w# 1 sw Y   0xY w# 1 sw Y   4xY w)u1   프로세스 없음 + 보고서 있음 → 전환r]   rM   ztask-123.mdrO   rP   rd   Nfind_recent_reportr*   r_   Tre   u	   보고서rf   )r   r6   rX   fake_reportrY   rZ   s         r   $test_report_file_exists_returns_truez?TestShouldTransitionToIdle.test_report_file_exists_returns_true   s    \\(,,/)B2GG
.LL#5BG	ZLL#:N	Z LL#7kR	Z
 &??
TXYNFF	Z 	Z 	Z ~~f$$$f$$$	Z 	Z 	Z 	Z 	Z 	Zs<   "C:?C.C"7C.?C:"C+'C..C7	3C::Dc                    t        j                  t        j                        t	        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d       d
u sJ dv sJ y# 1 sw Y   &xY w# 1 sw Y   *xY w# 1 sw Y   .xY w)uO   프로세스 없음 + .done 없음 + 보고서 없음 + 30분 초과 → 전환r]   rM   rO   rP   rd   Nrk   r*   r_   TtimeoutrS   )r   rX   rY   rZ   s       r   ,test_timeout_no_process_no_done_returns_truezGTestShouldTransitionToIdle.test_timeout_no_process_no_done_returns_true   s    \\(,,/)B2GG
LL#5BG	ZLL#:N	Z LL#7dK	Z
 &??
TXYNFF	Z 	Z 	Z ~~F"""	Z 	Z 	Z 	Z 	Z 	Zs<   C*CC"C*CCCC	CC(N)	r    r!   r"   r#   r[   rb   ri   rm   rp   r$   r   r   rJ   rJ   b   s    &'!!%
#r   rJ   c                   .    e Zd ZdZd Zd Zd Zd Zd Zy)TestFindBotProcessu   find_bot_process 테스트c                 <    t         j                  d      }|g k(  sJ y)u$   알 수 없는 봇 → 빈 리스트unknown_botN)r   rO   r   s     r   test_unknown_bot_returns_emptyz1TestFindBotProcess.test_unknown_bot_returns_empty   s    **=9||r   c                     t        dt        d            5  t        j                  d      }ddd       g k(  sJ y# 1 sw Y   xY w)u%   pgrep 실패 시 빈 리스트 반환subprocess.runzpgrep not foundside_effectr*   N)r   OSErrorr   rO   r   s     r    test_pgrep_failure_returns_emptyz3TestFindBotProcess.test_pgrep_failure_returns_empty   sD    #9J1KL 	7..v6F	7||	7 	7s	   =Ac                     ddl }t        d|j                  dgd            5  t        j	                  d      }ddd       g k(  sJ y# 1 sw Y   xY w)u+   pgrep 타임아웃 → 빈 리스트 반환r   Nrw   pgrep   rx   r*   )
subprocessr   TimeoutExpiredr   rO   )r   _subprocessr   s      r    test_pgrep_timeout_returns_emptyz3TestFindBotProcess.test_pgrep_timeout_returns_empty   sQ    (#1K1KWIWX1YZ 	7..v6F	7||	7 	7   A		Ac                      t        ddddd             }t        d|      5  t        j                  d      }d	d	d	       g k(  sJ y	# 1 sw Y   xY w)
u%   pgrep 매칭 없음 → 빈 리스트Rr$      r   
returncodestdoutrw   rP   r*   Ntyper   r   rO   r   mock_resultr   s      r   !test_pgrep_no_match_returns_emptyz4TestFindBotProcess.test_pgrep_no_match_returns_empty   sV    Dd31$CDF#+> 	7..v6F	7||	7 	7r   c                      t        ddddd             }t        d|      5  t        j                  d      }d	d	d	       d
dgk(  sJ y	# 1 sw Y   xY w)u   pgrep 성공 → PID 목록r   r$   r   z12345
67890
r   rw   rP   r*   Nr^   i2	 r   r   s      r   test_pgrep_returns_pidsz*TestFindBotProcess.test_pgrep_returns_pids   s]    Rd31@P$QRT#+> 	7..v6F	7%'''	7 	7s   AAN)	r    r!   r"   r#   ru   r{   r   r   r   r$   r   r   rr   rr      s    $
(r   rr   c                   :    e Zd ZdZd Zd Zd Zd Zd Zd Z	d Z
y	)
TestCheckAndRecoverStuckBotsu!   stuck 봇 자동 복구 테스트c                    |dz  }t        j                  t        j                        t	        d      z
  j                  d      }ddd|dii}|j                  t        j                  |             |t        _
        |d	z  t        _        t        j                  t        d
g       5  t        j                         }ddd       dk(  sJ t        j                  |j!                               }|d   d   d   dk(  sJ y# 1 sw Y   DxY w)uH   30분 초과 processing 봇 → idle 전환 (프로세스 없음 모킹)r(   r]   rM   %Y-%m-%dT%H:%M:%SZr)   r*   
processingr-   watchdog.logrO   rP   Nr   r.   r+   r   rT   r   r   r   strftimer0   r1   r2   r   r3   WATCHDOG_LOGr   rU   check_and_recover_stuck_botsrB   rC   r   r6   r7   forty_min_agorD   	recoveredrE   s          r   test_stuck_bot_recoveredz5TestCheckAndRecoverStuckBots.test_stuck_bot_recovered   s    11!hll3i6KKUUVjkL=!QRSDJJt,-%-" (> 9\\($6RH 	@ ==?I	@A~~

8--/0V}V$X.&888	@ 	@   'C??Dc                    |dz  }t        j                  t        j                        t	        d      z
  j                  d      }ddd|dii}|j                  t        j                  |             |t        _
        |d	z  t        _        t        j                  t        d
g       5  t        j                         }ddd       dk(  sJ t        j                  |j!                               }|d   d   d   dk(  sJ y# 1 sw Y   DxY w)u&   10분 미만 processing 봇 → 유지r(   rL   rM   r   r)   dev2r   r-   r   rO   rP   Nr   r.   r   )r   r6   r7   ten_min_agorD   r   rE   s          r   $test_recent_processing_not_recoveredzATestCheckAndRecoverStuckBots.test_recent_processing_not_recovered   s    11||HLL1Ib4IISSThiL;!OPQDJJt,-%-" (> 9\\($6RH 	@ ==?I	@A~~

8--/0V}V$X.,>>>	@ 	@r   c                 2   |dz  }dddddii}|j                  t        j                  |             |t        _        |dz  t        _        t        j                  t        dg 	      5  t        j                         }d
d
d
       dk(  sJ y
# 1 sw Y   xY w)u    idle 상태 봇은 변경 없음r(   r)   dev3r+   2026-01-01T00:00:00Zr-   r   rO   rP   Nr   	r0   r1   r2   r   r3   r   r   rU   r   r   r6   r7   rD   r   s        r   test_idle_bot_not_affectedz7TestCheckAndRecoverStuckBots.test_idle_bot_not_affected   s    11F=S!TUVDJJt,-%-" (> 9\\($6RH 	@ ==?I	@A~~	@ 	@   (BBc                    |dz  }|dz  }t        j                  t        j                        t	        d      z
  j                  d      }ddd|d	ii}|j                  t        j                  |             |t        _
        |t        _        t        j                  t        d
g       5  t        j                          ddd       |j                         sJ |j!                         }d|v sJ y# 1 sw Y   2xY w)u#   복구 시 [WATCHDOG] 로그 기록r(   r   r]   rM   r   r)   r*   r   r-   rO   rP   Nu$   [WATCHDOG] dev1: processing → idle)r   rT   r   r   r   r   r0   r1   r2   r   r3   r   r   rU   r   rA   rC   )r   r6   r7   log_filer   rD   contents          r   test_watchdog_log_entry_writtenz<TestCheckAndRecoverStuckBots.test_watchdog_log_entry_written   s    11n,!hll3i6KKUUVjkL=!QRSDJJt,-%-" (\\($6RH 	4113	4    $$&5@@@	4 	4s   )C//C8c                    |dz  }t        j                  t        j                        t	        d      z
  j                  d      }dd|dd|ddd	dd
i}|j                  t        j                  |             |t        _
        |dz  t        _        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]   rM   r   r)   r   r-   r+   r   )r*   r   r   r   rO   rP   N   )r   rT   r   r   r   r   r0   r1   r2   r   r3   r   r   rU   r   )r   r6   r7   r   rD   r   s         r   &test_multiple_stuck_bots_all_recoveredzCTestCheckAndRecoverStuckBots.test_multiple_stuck_bots_all_recovered  s    11!hll3i6KKUUVjk#/-H#/-H#)4JK
 	DJJt,-%-" (> 9\\($6RH 	@ ==?I	@A~~	@ 	@s   /CCc                    |dz  }t        j                  t        j                        t	        d      z
  j                  d      }ddd|dii}|j                  t        j                  |             |t        _
        |d	z  t        _        t        j                  t        d
dg      5  t        j                         }ddd       dk(  sJ t        j                  |j!                               }|d   d   d   dk(  sJ y# 1 sw Y   DxY w)u0   프로세스 살아있으면 idle 전환 안 됨r(   r]   rM   r   r)   r   r   r-   r   rO   i rP   Nr   r.   r   r   s          r   +test_process_alive_prevents_idle_transitionzHTestCheckAndRecoverStuckBots.test_process_alive_prevents_idle_transition  s    11!hll3i6KKUUVjkL=!QRSDJJt,-%-" (> 9\\($6eWM 	@ ==?I	@A~~

8--/0V}V$X.,>>>	@ 	@s   (D  D	c                 2   |dz  }dddddii}|j                  t        j                  |             |t        _        |dz  t        _        t        j                  t        dg 	      5  t        j                         }d
d
d
       dk(  sJ y
# 1 sw Y   xY w)u$   since 파싱 실패한 봇은 스킵r(   r)   r*   r   zinvalid-dater-   r   rO   rP   Nr   r   r   s        r   test_invalid_since_skippedz7TestCheckAndRecoverStuckBots.test_invalid_since_skipped(  s    11L>!RSTDJJt,-%-" (> 9\\($6RH 	@ ==?I	@A~~	@ 	@r   N)r    r!   r"   r#   r   r   r   r   r   r   r   r$   r   r   r   r      s)    +9 ? 
A"&? 
r   r   c                       e Zd ZdZd Zy)TestRunOnceu   1회 실행 테스트c           	          |dz  }|j                  t        j                  dddddii             |t        _        |dz  t        _        t        j                          |dz  j                         sJ y)	u"   stuck 봇 없을 때 정상 실행r(   r)   r*   r+   r,   r-   r   N)r0   r1   r2   r   r3   r   run_oncerA   )r   r6   r7   s      r   test_run_once_no_stuck_botsz'TestRunOnce.test_run_once_no_stuck_bots8  sn    11DJJFUk9l0m'nop%-" (> 9 	>)11333r   N)r    r!   r"   r#   r   r$   r   r   r   r   5  s
    
4r   r   __main__z-v) r#   r1   ossystempfiler   r   r   pathlibr   unittest.mockr   pytestmkdtemp_TEMP_WORKSPACEenvironpathinsertstr__file__parent
__import__r   r
   r&   r>   rJ   rr   r   r   r    mainr$   r   r   <module>r      s    0  	 
  2 2    #(""$.

  3tH~,,334 5+, 4& &4< <0;# ;#|"( "(Jm m`4 4  zFKK4 ! r   