
    4i_l                     f   d Z ddlZddlmc mZ ddlZddlZddl	Z	ddl
Z
ddlZddlmZ ddlmZmZ ddlZdZee
j&                  vre
j&                  j)                  de       ddlmZmZmZ ddlmZ 	 ddlmZmZ 	 dd	lmZmZm Z  	 ddlm!Z!m"Z"m#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*y# e$ r dZdZY ^w xY w# e$ r	 d
ZdZ dZY dw xY w# e$ r= e	j&                  jI                  ed      Z!e	j&                  jI                  ed      Z#dZ"Y w xY w)u0  TDD RED Phase 5 테스트 모음.

대상 모듈 (아직 미구현):
  - orchestrator.token_ledger : TokenLedger 클래스 Phase 5 확장 메서드
  - orchestrator.auto_orch    : send_telegram_alert, check_stale_tasks, 확장된 함수들

작성자 : 모리건 (dev3-team tester)
날짜   : 2026-03-24
    N)Path)	MagicMockpatchz/home/jay/workspace)cmd_scan
cmd_statusupdate_health)TokenLedger)check_stale_taskssend_telegram_alert)CONSERVATIVE_MULTIPLIERTOKENS_PER_MINUTE_OPUSTOKENS_PER_MINUTE_SONNETi  i  g333333?)ALERTS_SENT_PATHSTALE_TASK_RUNNING_SECONDSTASK_TIMERS_PATHz#orchestrator/state/alerts_sent.jsonzmemory/task-timers.jsoni   c                   @    e Zd ZdZd Zd Zd Zd Zd Zd Z	d Z
d	 Zy
)TestTokenLedgerPhase5u9   TokenLedger Phase 5 확장 메서드 테스트 스위트.c                    |dz  }t        t        |            }|j                  dd      }d}||k(  }|st        j                  d|fd||f      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      dz  }t        j                  d	|       d
z   d|iz  }t        t        j                  |            dx}}y)uD   Sonnet 세션(dev1-team, 600초) → 3000 * 10 * 1.2 = 36000 토큰.token_ledger.json	dev1-teamg     @i  ==z%(py0)s == %(py3)sresultpy0py3u4   Sonnet 세션(600초) 추정값이 36000이 아님: 
>assert %(py5)spy5Nr	   strestimate_session_tokens
@pytest_ar_call_reprcompare@py_builtinslocals_should_repr_global_name	_saferepr_format_assertmsgAssertionError_format_explanation	selftmp_pathledger_pathledgerr   @py_assert2@py_assert1@py_format4@py_format6s	            5/home/jay/workspace/orchestrator/tests/test_phase5.py#test_estimate_session_tokens_sonnetz9TestTokenLedgerPhase5.test_estimate_session_tokens_sonnetW   s    !44S-.//UC _v___v______v___v______"VW]V^ _______    c                    |dz  }t        t        |            }|j                  dd      }|j                  dd      }||kD  }|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      dz   d|iz  }t        t        j                  |            d}d}	||	k(  }|st        j                  d|fd||	f      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |	      dz  }
t        j                  d|       dz   d|
iz  }t        t        j                  |            dx}}	y)uT   Opus 세션(팀장 포함 team_id, 120초) → Sonnet보다 높은 추정값 반환.r   r   g      ^@u   팀장-dev1-team)>)z%(py0)s > %(py2)sopus_resultsonnet_resultr   py2u   Opus 세션 추정값(u   )이 Sonnet 세션 추정값(u   )보다 크지 않음
>assert %(py4)spy4Ni.  r   r   r   u2   Opus 세션(120초) 추정값이 12000이 아님: r   r   r    )r-   r.   r/   r0   r;   r:   r2   @py_format3@py_format5r1   r3   r4   s               r5   !test_estimate_session_tokens_opusz7TestTokenLedgerPhase5.test_estimate_session_tokens_opusa   s   !44S-. 66{EJ 445GO -'	sarar	s-	s 	slrlr	s 	sZrZr 	s 	sirir 	s 	slrlr	s 	sZrZr (	s 	sirir (	s 	sarar#K=0Mm_\qr	s 	s 	s_r_r	s 	s $g{e#ggg{egggggg{ggg{gggeggg'YZeYf%gggggggr7   c                    |dz  }t        t        |            }|j                  dd      }d}||k(  }|st        j                  d|fd||f      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      dz  }t        j                  d	|       d
z   d|iz  }t        t        j                  |            dx}}y)u    0초 세션 → 0 토큰 반환.r   r           r   r   r   r   r   u&   0초 세션 추정값이 0이 아님: r   r   Nr    r,   s	            r5   *test_estimate_session_tokens_zero_durationz@TestTokenLedgerPhase5.test_estimate_session_tokens_zero_durationt   s    !44S-.//SAMv{MMMvMMMMMMvMMMvMMMMMMDVHMMMMMMMr7   c                    |dz  }t         j                  j                         j                         dddid}|j	                  t        j                  |      d       t        t        |            }|j                         }|d   }d}||k(  }|st        j                  d|fd	||f      t        j                  |      t        j                  |      d
z  }	t        j                  d|d          dz   d|	iz  }
t        t        j                  |
            dx}x}}|d   }d}||k(  }|st        j                  d|fd	||f      t        j                  |      t        j                  |      d
z  }	t        j                  d|d          dz   d|	iz  }
t        t        j                  |
            dx}x}}|d   }d}||k(  }|st        j                  d|fd	||f      t        j                  |      t        j                  |      d
z  }	t        j                  d|d          dz   d|	iz  }
t        t        j                  |
            dx}x}}|d   }d}||k(  }|st        j                  d|fd	||f      t        j                  |      t        j                  |      d
z  }	t        j                  d|d          dz   d|	iz  }
t        t        j                  |
            dx}x}}y)ud   daily_total=450000 → {"today": 450000, "limit": 1000000, "remaining": 550000, "percentage": 45.0}.r    zpipe-001datedaily_total	pipelinesutf-8encodingtodayr   z%(py1)s == %(py4)spy1r?   u   today 값 불일치: 
>assert %(py6)spy6Nlimit@B u   limit 값 불일치: 	remainingipd u   remaining 값 불일치: 
percentage     F@u   percentage 값 불일치: )datetimerI   rO   	isoformat
write_textjsondumpsr	   r!   get_daily_usage_summaryr#   r$   r(   r)   r*   r+   )r-   r.   r/   stater0   summary@py_assert0@py_assert3r1   rA   @py_format7s              r5   test_get_daily_usage_summaryz2TestTokenLedgerPhase5.test_get_daily_usage_summary}   s#   !44 MM'')335!$f-

 	tzz%07CS-.002wU6U6)UUU6UUUUUU6UUU-B77CSBT+UUUUUUUUwX9X9,XXX9XXXXXX9XXX0EggFVEW.XXXXXXXX{#ava#v-aaa#vaaa#aaavaaa1J7S^K_J`/aaaaaaaa|$bb$,bbb$bbb$bbbbbb0J7S_K`Ja.bbbbbbbbr7   c                 6   |dz  }t        t        |            }|j                         }|d   }d}||k(  }|st        j                  d|fd||f      t        j
                  |      t        j
                  |      dz  }t        j                  d|d          dz   d	|iz  }	t        t        j                  |	            d
x}x}}|d   }d}||k(  }|st        j                  d|fd||f      t        j
                  |      t        j
                  |      dz  }t        j                  d|d          dz   d	|iz  }	t        t        j                  |	            d
x}x}}|d   }d}||k(  }|st        j                  d|fd||f      t        j
                  |      t        j
                  |      dz  }t        j                  d|d          dz   d	|iz  }	t        t        j                  |	            d
x}x}}|d   }d}||k(  }|st        j                  d|fd||f      t        j
                  |      t        j
                  |      dz  }t        j                  d|d          dz   d	|iz  }	t        t        j                  |	            d
x}x}}y
)uZ   신규 레저 → {"today": 0, "limit": 1000000, "remaining": 1000000, "percentage": 0.0}.ztoken_ledger_empty.jsonrO   r   r   rP   rQ   u#   신규 레저 today 값 불일치: rS   rT   NrU   rV   u#   신규 레저 limit 값 불일치: rW   u'   신규 레저 remaining 값 불일치: rX   rD   u(   신규 레저 percentage 값 불일치: )	r	   r!   r_   r#   r$   r(   r)   r*   r+   )
r-   r.   r/   r0   ra   rb   rc   r1   rA   rd   s
             r5   "test_get_daily_usage_summary_emptyz8TestTokenLedgerPhase5.test_get_daily_usage_summary_empty   s   !::S-.002w^1^1$^^^1^^^^^^1^^^(KGT[L\K]&^^^^^^^^wf9f9,fff9ffffff9fff0ST[\cTdSe.ffffffff{#ryr#y0rrr#yrrr#rrryrrr4[\cdo\p[q2rrrrrrrr|$oo$+ooo$ooo$oooooo/WX_`lXmWn-oooooooor7   c                    |dz  }t         j                  j                         j                         di d}|j	                  t        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  }t        j                   d|       dz   d|iz  }	t#        t        j$                  |	            dx}}y)u>   daily_total=700000, threshold=0.8 → False (700000 < 800000).r   i`
 rH   rL   rM   皙?	thresholdFisz%(py0)s is %(py3)sr   r   uD   700000 < 800000인데 check_warning_threshold가 True를 반환함: r   r   NrZ   rI   rO   r[   r\   r]   r^   r	   r!   check_warning_thresholdr#   r$   r%   r&   r'   r(   r)   r*   r+   
r-   r.   r/   r`   r0   r   r1   r2   r3   r4   s
             r5   "test_check_warning_threshold_belowz8TestTokenLedgerPhase5.test_check_warning_threshold_below   s    !44MM'')335!

 	tzz%07CS-.//#/>ovooovoooooovooovoooooo"fgmfn ooooooor7   c                    |dz  }t         j                  j                         j                         di d}|j	                  t        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  }t        j                   d|       dz   d|iz  }	t#        t        j$                  |	            dx}}y)u>   daily_total=800000, threshold=0.8 → True (800000 == 800000).r   i 5 rH   rL   rM   ri   rj   Trl   rn   r   r   uF   800000 == 800000인데 check_warning_threshold가 False를 반환함: r   r   Nro   rq   s
             r5   test_check_warning_threshold_atz5TestTokenLedgerPhase5.test_check_warning_threshold_at   s    !44MM'')335!

 	tzz%07CS-.//#/>pv~pppvppppppvpppvpppppp!ghngopppppppr7   c                    |dz  }t         j                  j                         j                         di d}|j	                  t        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  }t        j                   d|       dz   d|iz  }	t#        t        j$                  |	            dx}}y)u=   daily_total=900000, threshold=0.8 → True (900000 > 800000).r   頻 rH   rL   rM   ri   rj   Trl   rn   r   r   uE   900000 > 800000인데 check_warning_threshold가 False를 반환함: r   r   Nro   rq   s
             r5   "test_check_warning_threshold_abovez8TestTokenLedgerPhase5.test_check_warning_threshold_above   s    !44MM'')335!

 	tzz%07CS-.//#/>ov~ooovoooooovooovoooooo!fgmfnooooooor7   N)__name__
__module____qualname____doc__r6   rB   rE   re   rg   rr   rt   rw    r7   r5   r   r   T   s5    C`h&Nc&
ppqpr7   r   c                   (    e Zd ZdZd Zd Zd Zd Zy)TestTelegramAlertu/   send_telegram_alert 함수 테스트 스위트.c                 	   t         t        j                  d       |dz  }|j                  dd       |j                  dd       |j	                  dt        |             t               }d	|_        t        d
|      5 }t        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  }	t        j                  d|       dz   d|	iz  }
t!        t        j"                  |
            dx}}j$                  }|st        j                  d      dz   dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      dz  }t!        t        j"                  |            d}|j&                  } |       }|st        j                  d      dz   dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      dz  }t!        t        j"                  |            dx}}t)        j*                  |j-                  d            }t.        j0                  j3                         j5                         }||v }|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}d}||   }||v }|st        j                  d |fd(||f      t        j                  |      t        j                  |      d)z  }t        j                  d*|       d+z   d,|iz  }t!        t        j"                  |            dx}x}}y# 1 sw Y   xY w)-uJ   mock requests.post → 200 → True 반환, alerts_sent.json에 기록됨.ND   send_telegram_alert가 orchestrator.auto_orch에 구현되지 않음alerts_sent.jsonANU_BOT_TOKENtest-bot-token-12345AUTO_ORCH_CHAT_ID	987654321'orchestrator.auto_orch.ALERTS_SENT_PATH   requests.postreturn_valueu   토큰 경고: 80% 초과token_warningTrl   rn   r   r   u<   200 응답인데 send_telegram_alert가 False를 반환함: r   r   u$   requests.post가 호출되지 않음,
>assert %(py2)s
{%(py2)s = %(py0)s.called
}	mock_postr<   u'   alerts_sent.json이 생성되지 않음C
>assert %(py4)s
{%(py4)s = %(py2)s
{%(py2)s = %(py0)s.exists
}()
}alerts_pathr   r=   r?   rL   rM   in)z%(py0)s in %(py2)srO   datau"   alerts_sent.json에 오늘 날짜(u   )가 없음: r>   r?   )z%(py1)s in %(py4)srQ   u*   alerts_sent.json에 alert_type이 없음: rS   rT   )r   pytestfailsetenvsetattrr!   r   status_coder   r#   r$   r%   r&   r'   r(   r)   r*   r+   calledexistsr]   loads	read_textrZ   rI   rO   r[   )r-   r.   monkeypatchr   mock_responser   r   r1   r2   r3   r4   r@   rc   rA   r   rO   rb   rd   s                     r5    test_send_telegram_alert_successz2TestTelegramAlert.test_send_telegram_alert_success   s   &KK^_!33?,BC.<Es;GWX!$'!?? 	W9()DoVF	W fv~fffvffffffvfffvffffff!]^d]efffffffGGG!GGGGGGGyGGGyGGGGGGGGG !!N!#N#NN%NNNNNNN{NNN{NNN!NNN#NNNNNNzz+///AB##%//1}]]]u]]]]]]u]]]u]]]]]]]]]]]]] B5'W[V\]]]]]]]b$u+b+-bbb+bbbbbb+bbb1[\`[a/bbbbbbbb	W 	Ws   >R  R
c                 R   t         t        j                  d       |dz  }|j                  dd       |j                  dd       |j	                  dt        |             t               }d	|_        t        d
|      5  t        dd      }t        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  }	t        j                  d|       dz   d|	iz  }
t!        t        j"                  |
            dx}}d}|u }|st        j                  d|fd||f      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      dz  }	t        j                  d|       dz   d|	iz  }
t!        t        j"                  |
            dx}}y# 1 sw Y   xY w)u@   같은 날 같은 alert_type → 두 번째 호출 False 반환.Nr   r   r   r   r   r   r   r   r   r   u   첫 번째 경고r   u   두 번째 경고Trl   rn   first_resultr   u%   첫 번째 호출이 True가 아님: r   r   Fsecond_resultu>   중복 alert_type인데 두 번째 호출이 False가 아님: )r   r   r   r   r   r!   r   r   r   r#   r$   r%   r&   r'   r(   r)   r*   r+   )r-   r.   r   r   r   r   r   r1   r2   r3   r4   s              r5   test_send_telegram_alert_dedupz0TestTelegramAlert.test_send_telegram_alert_dedup   sz   &KK^_!33?,BC.<Es;GWX!$'!?? 	V./BOTL/0C_UM	V  $[|t#[[[|t[[[[[[|[[[|[[[t[[['L\N%[[[[[[[ %w}%www}wwwwww}www}wwwwww)ghugv'wwwwwww	V 	Vs   >HH&c                 X   t         t        j                  d       |dz  }|j                  dd       |j	                  dd       |j                  d	t        |             t        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  }t        j                  d|       dz   d|iz  }t        t        j                  |            dx}}y)u)   ANU_BOT_TOKEN 미설정 → False 반환.Nr   r   r   F)raisingr   r   r   u   경고 메시지r   rl   rn   r   r   u1   ANU_BOT_TOKEN 미설정인데 True를 반환함: r   r   )r   r   r   delenvr   r   r!   r#   r$   r%   r&   r'   r(   r)   r*   r+   )	r-   r.   r   r   r   r1   r2   r3   r4   s	            r5   !test_send_telegram_alert_no_tokenz3TestTelegramAlert.test_send_telegram_alert_no_token   s    &KK^_!33?E:.<Es;GWX$%7I\v\\\v\\\\\\v\\\v\\\\\\"STZS[ \\\\\\\r7   c                    t         t        j                  d       |dz  }t        j                  j                         t        j                  d      z
  j                         }|dgi}|j                  t        j                  |      d       |j                  d	d
       |j                  dd       |j                  dt        |             t               }d|_        t!        d|      5  t        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  }
t#        j.                  d|       dz   d|
iz  }t1        t#        j2                  |            dx}	}y# 1 sw Y   xY w)uI   날짜 변경 시 기존 기록 무시, 재발송 가능 → True 반환.Nr   r      )daysr   rL   rM   r   r   r   r   r   r   r   r   u   오늘 새 경고Trl   rn   r   r   u6   날짜 변경 후 재발송인데 False를 반환함: r   r   )r   r   r   rZ   rI   rO   	timedeltar[   r\   r]   r^   r   r   r!   r   r   r   r#   r$   r%   r&   r'   r(   r)   r*   r+   )r-   r.   r   r   	yesterdayexisting_datar   r   r1   r2   r3   r4   s               r5    test_send_telegram_alert_new_dayz2TestTelegramAlert.test_send_telegram_alert_new_day  sW   &KK^_!33]]((*X-?-?Q-GGRRT	"_$56tzz-87K?,BC.<Es;GWX!$'!?? 	O()<oNF	O `v~```v``````v```v``````!WX^W_```````	O 	Os   ,G  G	N)rx   ry   rz   r{   r   r   r   r   r|   r7   r5   r~   r~      s    9c4x(]ar7   r~   c                       e Zd ZdZd Zd Zy)TestStaleTaskChecku-   check_stale_tasks 함수 테스트 스위트.c                 `   t         t        j                  d       |dz  }|j                  dt	        |             t
        j
                  j                  t
        j                  j                        }|t        j                  d      z
  j                         }|t        j                  d      z
  j                         }d	d
|ddd
|ddd
|dd	d|dd}|j                  t        j                  |      d       t               }t        |t              }	|	s-t!        j"                  dt%        |             dz   dt'        j(                         v st!        j*                  t              rt!        j,                  t              nddt'        j(                         v st!        j*                  |      rt!        j,                  |      nddt'        j(                         v st!        j*                  t              rt!        j,                  t              ndt!        j,                  |	      dz  }
t/        t!        j0                  |
            d}	t3        |      }d}||k(  }|st!        j4                  d|fd||f      dt'        j(                         v st!        j*                  t2              rt!        j,                  t2              nddt'        j(                         v st!        j*                  |      rt!        j,                  |      ndt!        j,                  |      t!        j,                  |      dz  }t!        j"                  dt3        |       d|       dz   d |iz  }t/        t!        j0                  |            dx}x}}|D cg c]  }|d!   	 }}d"}||v }|st!        j4                  d#|fd$||f      t!        j,                  |      d%t'        j(                         v st!        j*                  |      rt!        j,                  |      nd%d&z  }t!        j"                  d'|       d(z   d)|iz  }t/        t!        j0                  |            dx}}d*}||v }|st!        j4                  d#|fd$||f      t!        j,                  |      d%t'        j(                         v st!        j*                  |      rt!        j,                  |      nd%d&z  }t!        j"                  d+|       d(z   d)|iz  }t/        t!        j0                  |            dx}}d,}||v}|st!        j4                  d-|fd.||f      t!        j,                  |      d%t'        j(                         v st!        j*                  |      rt!        j,                  |      nd%d&z  }t!        j"                  d/|       d(z   d)|iz  }t/        t!        j0                  |            dx}}d0}||v}|st!        j4                  d-|fd.||f      t!        j,                  |      d%t'        j(                         v st!        j*                  |      rt!        j,                  |      nd%d&z  }t!        j"                  d1|       d(z   d)|iz  }t/        t!        j0                  |            dx}}|D ]  }d!}||v }|st!        j4                  d#|fd$||f      t!        j,                  |      d2t'        j(                         v st!        j*                  |      rt!        j,                  |      nd2d&z  }t!        j"                  d3|       d(z   d)|iz  }t/        t!        j0                  |            dx}}d4}||v }|st!        j4                  d#|fd$||f      t!        j,                  |      d2t'        j(                         v st!        j*                  |      rt!        j,                  |      nd2d&z  }t!        j"                  d5|       d(z   d)|iz  }t/        t!        j0                  |            dx}}d6}||v }|st!        j4                  d#|fd$||f      t!        j,                  |      d2t'        j(                         v st!        j*                  |      rt!        j,                  |      nd2d&z  }t!        j"                  d7|       d(z   d)|iz  }t/        t!        j0                  |            dx}}|d6   }|t6        k\  }|st!        j4                  d8|fd9|t6        f      t!        j,                  |      d:t'        j(                         v st!        j*                  t6              rt!        j,                  t6              nd:d&z  }t!        j"                  d;|d6    d<t6         d=      d(z   d)|iz  }t/        t!        j0                  |            dx}} yc c}w )>uA   task-timers.json에 2시간+ running 태스크 → 목록 반환.NB   check_stale_tasks가 orchestrator.auto_orch에 구현되지 않음task-timers.json'orchestrator.auto_orch.TASK_TIMERS_PATH   hours   minutesr   runningteam_idstatus
start_time	dev2-teamz	dev3-teamdone)stale-task-001stale-task-002fresh-task-001done-task-001rL   rM   /   check_stale_tasks 반환값이 list가 아님: 7
>assert %(py4)s
{%(py4)s = %(py0)s(%(py1)s, %(py2)s)
}
isinstancer   listr   rR   r=   r?      r   z0%(py3)s
{%(py3)s = %(py0)s(%(py1)s)
} == %(py6)slenr   rR   r   rT   u(   stale 태스크가 2개여야 하는데    개 반환됨: 
>assert %(py8)spy8task_idr   r   z%(py1)s in %(py3)stask_idsrR   r   u$   stale-task-001이 결과에 없음: r   r   r   u$   stale-task-002이 결과에 없음: r   not in)z%(py1)s not in %(py3)su.   fresh 태스크가 stale 목록에 포함됨: r   u-   done 태스크가 stale 목록에 포함됨: itemu   task_id 키 없음: r   u   team_id 키 없음: duration_secondsu   duration_seconds 키 없음: )>=)z%(py1)s >= %(py3)sr   zduration_seconds(u   )가 임계값(u   )보다 작음)r
   r   r   r   r!   rZ   nowtimezoneutcr   r[   r\   r]   r^   r   r   r#   r)   typer%   r&   r'   r(   r*   r+   r   r$   r   )r-   r.   r   task_timers_pathr   stale_startfresh_starttask_timers_datar   rc   rA   r1   @py_assert5@py_assert4rd   @py_format9r   r   rb   r3   r4   s                        r5   test_check_stale_tasks_foundz/TestStaleTaskCheck.test_check_stale_tasks_found.  si   $KK\]#&88EsK[G\] ##H$5$5$9$9:X//a88CCEX//;;FFH '#) '#) '#) ' )!
, 	##DJJ/?$@7#S"$&$'i'ii+Z[_`f[gZh)iiiiiiiziiiziiiiii&iii&iiiiii$iii$iii'iiiiii6{pap{appp{appppppspppspppppp6ppp6ppp{pppappp#KCPVK=Xghngo!pppppppp067DO77^8+^^^8^^^^^^^^^8^^^8^^^^/ST\S]-^^^^^^^^8+^^^8^^^^^^^^^8^^^8^^^^/ST\S]-^^^^^^^lx/lllxlllllllllxlllxllll3abjak1llllllljh.jjjhjjjjjjjjjhjjjhjjjj2_`h_i0jjjjjjj  	wDC9$CCC9CCC9CCCCCCCCCCCCC(<TF&CCCCCCCC9$CCC9CCC9CCCCCCCCCCCCC(<TF&CCCCCCC%U%-UUU%UUU%UUUUUUUUUUUUU1Ntf/UUUUUUU'(w(,FFwevevw(,Fw wmvmv )w wpvpvw w^v^v -Gw wmvmv -Gw wevev"4(:#;"<OLfKgguvw w wcvcvw w		w 8s   f+c                    t         t        j                  d       |dz  }|j                  dt	        |             t
        j
                  j                  t
        j                  j                        }|t        j                  d      z
  j                         }dd|d	d
d|t        j                  d      z
  j                         d	d}|j                  t        j                  |      d       t               }t        |t              }|s-t!        j"                  dt%        |             dz   dt'        j(                         v st!        j*                  t              rt!        j,                  t              nddt'        j(                         v st!        j*                  |      rt!        j,                  |      nddt'        j(                         v st!        j*                  t              rt!        j,                  t              ndt!        j,                  |      dz  }	t/        t!        j0                  |	            d}t3        |      }
d}|
|k(  }|st!        j4                  d|fd|
|f      dt'        j(                         v st!        j*                  t2              rt!        j,                  t2              nddt'        j(                         v st!        j*                  |      rt!        j,                  |      ndt!        j,                  |
      t!        j,                  |      dz  }t!        j"                  dt3        |       d|       dz   d|iz  }t/        t!        j0                  |            dx}
x}}y) u0   stale 태스크 없음 → 빈 리스트 반환.Nr   r   r   r   r   r   r   r   r   r      r   )r   r   rL   rM   r   r   r   r   r   r   r   r   r   r   r   u'   stale 태스크가 없어야 하는데 r   r   r   )r
   r   r   r   r!   rZ   r   r   r   r   r[   r\   r]   r^   r   r   r#   r)   r   r%   r&   r'   r(   r*   r+   r   r$   )r-   r.   r   r   r   r   r   r   rc   rA   r1   r   r   rd   r   s                  r5   test_check_stale_tasks_nonez.TestStaleTaskCheck.test_check_stale_tasks_noneg  s/   $KK\]#&88EsK[G\]##H$5$5$9$9:X//;;FFH '#) ' "X%7%7a%@@KKM
 	##DJJ/?$@7#S"$&$'i'ii+Z[_`f[gZh)iiiiiiiziiiziiiiii&iii&iiiiii$iii$iii'iiiiii6{oao{aooo{aoooooosooosoooooo6ooo6ooo{oooaooo#J3v;-Wfgmfn!oooooooor7   N)rx   ry   rz   r{   r   r   r|   r7   r5   r   r   +  s    77wrpr7   r   c                       e Zd ZdZd Zd Zy)TestHealthJsonExtensionu1   update_health Phase 5 확장 테스트 스위트.c                 	   |dz  }|j                  dt        |             dddd}t        dd|	       |j                  } |       }|st	        j
                  d
      dz   dt        j                         v st	        j                  |      rt	        j                  |      ndt	        j                  |      t	        j                  |      dz  }t        t	        j                  |            dx}}t        j                  |j                  d            }d}	|	|v }
|
st	        j                  d|
fd|	|f      t	        j                  |	      dt        j                         v st	        j                  |      rt	        j                  |      nddz  }t	        j
                  d|       dz   d|iz  }t        t	        j                  |            dx}	}
|d   d   }	d}|	|k(  }
|
st	        j                  d|
fd|	|f      t	        j                  |	      t	        j                  |      dz  }t	        j
                  d|d          dz   d|iz  }t        t	        j                  |            dx}	x}
}|d   d    }	d}|	|k(  }
|
st	        j                  d|
fd|	|f      t	        j                  |	      t	        j                  |      dz  }t	        j
                  d!|d          dz   d|iz  }t        t	        j                  |            dx}	x}
}|d   d"   }	d}|	|k(  }
|
st	        j                  d|
fd|	|f      t	        j                  |	      t	        j                  |      dz  }t	        j
                  d#|d          dz   d|iz  }t        t	        j                  |            d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  }t	        j
                  d(|j!                  d$             d)z   d*|iz  }t        t	        j                  |            dx}x}x}x}}y)+u<   token_usage 전달 시 health.json에 포함, version "1.1".health.json"orchestrator.auto_orch.HEALTH_PATHrG   rV   rY   )rO   rU   rX   r   r   active_pipelineserrorstoken_usage4   update_health 후 health.json이 생성되지 않음r   health_pathr   NrL   rM   r   r   r   r   r   u'   health.json에 token_usage 키 없음: r   r   rO   r   rP   rQ   u   token_usage.today 불일치: rS   rT   rU   u   token_usage.limit 불일치: rX   u"   token_usage.percentage 불일치: versionz1.1)zI%(py6)s
{%(py6)s = %(py2)s
{%(py2)s = %(py0)s.get
}(%(py4)s)
} == %(py9)s)r   r=   r?   rT   py9u   version이 '1.1'이 아님: z
>assert %(py11)spy11)r   r!   r   r   r#   r)   r%   r&   r'   r(   r*   r+   r]   r   r   r$   get)r-   r.   r   r   r   r2   rc   rA   r   rb   r1   r3   r4   rd   r   @py_assert8@py_assert7@py_format10@py_format12s                      r5   #test_update_health_with_token_usagez;TestHealthJsonExtension.test_update_health_with_token_usage  s>   .@#kBRS 
 	qL!![!#[#[[%[[[[[[[{[[[{[[[![[[#[[[[[[zz+///ABV}$VVV}VVV}VVVVVVVVVVVVV(OPTv&VVVVVVVM"7+lvl+v5lll+vlll+lllvlll9VW[\iWjVk7llllllllM"7+oyo+y8ooo+yooo+oooyooo<YZ^_lZmYn:ooooooooM"<0tDt0D8ttt0Dttt0tttDttt<^_cdq_r^s:ttttttttxxa	ax	"aea"e+aaa"eaaaaaataaataaaxaaa	aaa"aaaeaaa/KDHHU^L_K`-aaaaaaaar7   c                 (	   |dz  }|j                  dt        |             t        ddd       |j                  } |       }|st	        j
                  d      dz   d	t        j                         v st	        j                  |      rt	        j                  |      nd	t	        j                  |      t	        j                  |      d
z  }t        t	        j                  |            dx}}t        j                  |j                  d            }d}||v }	|	st	        j                  d|	fd||f      t	        j                  |      dt        j                         v st	        j                  |      rt	        j                  |      nddz  }
t	        j
                  d|       dz   d|
iz  }t        t	        j                  |            dx}}	|d   }d}||k(  }	|	st	        j                  d|	fd||f      t	        j                  |      t	        j                  |      dz  }t	        j
                  d|       dz   d|iz  }t        t	        j                  |            dx}x}	}d}||v }	|	st	        j                  d|	fd||f      t	        j                  |      dt        j                         v st	        j                  |      rt	        j                  |      nddz  }
t	        j
                  d|       dz   d|
iz  }t        t	        j                  |            dx}}	g }d}	|	|v}|}|s|d   }d}||u }|}|sDt	        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!                  |       |s_t	        j                  d#fd$f      t	        j                  |      t	        j                  |      d%z  }d&d'|iz  }|j!                  |       t	        j"                  |d      i z  }t	        j
                  d(|       d)z   d*|iz  }t        t	        j                  |            dx}x}x}	x}x}x}}y)+u9   token_usage=None → 기존 포맷 유지 (하위호환).r   r   r   r   Nr   r   r   r   r   rL   rM   r   r   r   r   r   u   active_pipelines 키 없음: r   r   r   rP   rQ   u    active_pipelines 값 불일치: rS   rT   	last_ticku   last_tick 키 없음: r   r   )z%(py3)s not in %(py5)s)r   r   z%(py7)spy7rl   )z%(py10)s is %(py13)s)py10py13z%(py15)spy15u@   token_usage=None인데 health.json에 token_usage가 포함됨: z
>assert %(py18)spy18)r   r!   r   r   r#   r)   r%   r&   r'   r(   r*   r+   r]   r   r   r$   append_format_boolop)r-   r.   r   r   r2   rc   rA   r   rb   r1   r3   r4   rd   r   @py_assert9@py_assert12@py_assert11@py_format8@py_format14@py_format16@py_format17@py_format19s                         r5   &test_update_health_without_token_usagez>TestHealthJsonExtension.test_update_health_without_token_usage  s   .@#kBRSqE!![!#[#[[%[[[[[[[{[[[{[[[![[[#[[[[[[zz+///AB "Q!T)QQQ!TQQQ!QQQQQQTQQQTQQQQ-J4&+QQQQQQQ&'W1W'1,WWW'1WWW'WWW1WWW0PQUPV.WWWWWWWWC{d"CCC{dCCC{CCCCCCdCCCdCCCC&<TF$CCCCCCC	U	U%	U)-m)<	U@D	U)<)D	U 	UCTCT	U	U 	UKT9 	U 	UNTf	U 	U<T<T "&	U 	UKT9 "&	U 	U 	UNTf	UNT	UCTCT	U)<	U 	UKT9 *=	U 	UKT9 AE	U 	U 	UNTf	UNT	UFTn	U 	UCTCTMdVT	U 	U 	UATAT	U 	U 	U 	Ur7   N)rx   ry   rz   r{   r   r  r|   r7   r5   r   r     s    ;b,Ur7   r   c                   "    e Zd ZdZd Zd Zd Zy)TestCmdStatusExtensionu5   cmd_status Phase 5 확장 출력 테스트 스위트.c                 V   |dz  }|j                          |j                  dt        |             |dz  }|j                  dt        |             t        j                  j                         j                         di d}|j                  t        j                  |      d       ||fS )	u:   공통 fixture: state 디렉터리와 token_ledger 설정.r`    orchestrator.auto_orch.STATE_DIRr   "orchestrator.auto_orch.LEDGER_PATHrG   rH   rL   rM   )
mkdirr   r!   rZ   rI   rO   r[   r\   r]   r^   )r-   r.   r   	state_dirr/   ledger_states         r5   _setup_state_and_ledgerz.TestCmdStatusExtension._setup_state_and_ledger  s    w&	>IO"55@#kBRSMM'')335!

 	tzz,7'J+%%r7   c                 
   | j                  ||       t                |j                         }|j                  |j                  z   }d}||v }|st        j                  d|fd||f      t        j                  |      dt        j                         v st        j                  |      rt        j                  |      nddz  }t        j                  d|       dz   d|iz  }	t        t        j                  |	            d	x}}y	)
uI   cmd_status 출력에 '토큰 사용량' 섹션이 포함되어야 한다.u   토큰 사용량r   r   outputr   u7   cmd_status 출력에 '토큰 사용량' 섹션 없음:
r   r   Nr  r   
readouterrouterrr#   r$   r(   r%   r&   r'   r)   r*   r+   
r-   r.   r   capsyscapturedr  rb   r1   r3   r4   s
             r5   $test_cmd_status_includes_token_usagez;TestCmdStatusExtension.test_cmd_status_includes_token_usage  s    $$X{;$$&,!p!V+ppp!Vppp!ppppppVpppVpppp/ghngo-pppppppr7   c                 
   | j                  ||       t                |j                         }|j                  |j                  z   }d}||v }|st        j                  d|fd||f      t        j                  |      dt        j                         v st        j                  |      rt        j                  |      nddz  }t        j                  d|       dz   d|iz  }	t        t        j                  |	            d	x}}y	)
uL   cmd_status 출력에 '경고 임계값' 텍스트가 포함되어야 한다.u   경고 임계값r   r   r  r   u:   cmd_status 출력에 '경고 임계값' 텍스트 없음:
r   r   Nr  r  s
             r5   *test_cmd_status_includes_warning_thresholdzATestCmdStatusExtension.test_cmd_status_includes_warning_threshold  s    $$X{;$$&,!s!V+sss!Vsss!ssssssVsssVssss/jkqjr-sssssssr7   N)rx   ry   rz   r{   r  r"  r$  r|   r7   r5   r  r    s    ?&"qtr7   r  c                   "    e Zd ZdZd Zd Zd Zy)TestCmdScanIntegrationuH   cmd_scan Phase 5 통합 테스트 — 알림 및 stale 태스크 감지.c                 4   |dz  }|j                          |dz  }|j                          |dz  }|j                          |dz  }|dz  }|dz  }|dz  }	|j                  dt        |             |j                  d	t        |             |j                  d
t        |             |j                  dt        |             |j                  dt        |             |j                  dt        |             |j                  dt        |	             ||||||dS )u;   공통 fixture: cmd_scan 실행에 필요한 환경 설정.r`   incoming	processedr   r   r   r   r  z#orchestrator.auto_orch.INCOMING_DIRz$orchestrator.auto_orch.PROCESSED_DIRr   r   r   r  )r  incoming_dirprocessed_dirr   r   r   )r  r   r!   )
r-   r.   r   r  r*  r+  r   r   r   r/   s
             r5   _setup_scan_envz&TestCmdScanIntegration._setup_scan_env  s&   w&	*, ;..#&88!33"55>IOA3|CTUBCDVW@#kBRSEsK[G\]Es;GWX@#kBRS #(*& 0&
 	
r7   c                 z   t         t        j                  d       | j                  ||      }|d   dz  }t        j
                  j                         j                         di d}|j                  t        j                  |      d       |d	   j                  t        j                  i       d       t        d
      5 }d|_        t        dd      5  t        d      5  t                ddd       ddd       ddd       j                  }|st        j                   d      dz   dt#        j$                         v st        j&                  |      rt        j(                  |      ndt        j(                  |      dz  }t+        t        j,                  |            d}|j.                  }	|	D 
cg c]D  }
t1        |
j2                        dkD  r|
j2                  d   n|
j4                  j7                  d      F }}
t9        d |D              }|s~t        j                   d|       dz   ddt#        j$                         v st        j&                  |      rt        j(                  |      ndiz  }t+        t        j,                  |            y# 1 sw Y   xY w# 1 sw Y   xY w# 1 sw Y   xY wc c}
w )u=   80% 초과 시 send_telegram_alert가 호출되어야 한다.Nr   r  r   rv   rH   rL   rM   r   *orchestrator.auto_orch.send_telegram_alertT*orchestrator.auto_orch.acquire_global_lock*   r   *orchestrator.auto_orch.release_global_locku;   80% 초과인데 send_telegram_alert가 호출되지 않음r   
mock_alertr<   r   
alert_typec              3   ^   K   | ]%  }|d uxr dt        |      j                         v  ' y w)Ntokenr!   lower.0ats     r5   	<genexpr>zPTestCmdScanIntegration.test_cmd_scan_sends_alert_at_threshold.<locals>.<genexpr>,  ,      eSU4!NGs2w}}4N!N e   +-u3   token 관련 alert_type으로 호출되지 않음: 
>assert %(py0)sr   token_alert_called)r   r   r   r,  rZ   rI   rO   r[   r\   r]   r^   r   r   r   r   r#   r)   r%   r&   r'   r(   r*   r+   call_args_listr   argskwargsr   any)r-   r.   r   pathsr/   r  r2  r2   r@   r@  callalert_typesr?  @py_format1s                 r5   &test_cmd_scan_sends_alert_at_thresholdz=TestCmdScanIntegration.test_cmd_scan_sends_alert_at_threshold  s   &KK^_$$X{; K(+>>MM'')335!

 	tzz,7'J 	 !,,TZZ^g,N?@ 	J&*J#CRTU GH J	   _ __"_______z___z___ ______ $22iwxaes499~'9tyy|t{{|?\\xx  eYd ee!ff%XYdXe#fffffff!fff!fffff!  	 	 ysC    J+J!J,J4J+3A	J8JJJ(	#J++J5c                 |   t         t        j                  d       t        t        j                  d       | j	                  ||      }|d   dz  }t
        j                  j                         j                         di d}|j                  t        j                  |      d	       t
        j
                  j                  t
        j                  j                        }|t        j                  d
      z
  j                         }ddd|di}|d   j                  t        j                  |      d	       t!        d      5 }	d|	_        t!        dd      5  t!        d      5  t%                ddd       ddd       ddd       	j&                  }
|
st)        j*                  d      dz   dt-        j.                         v st)        j0                  |	      rt)        j2                  |	      ndt)        j2                  |
      dz  }t5        t)        j6                  |            d}
|	j8                  }|D cg c]D  }t;        |j<                        dkD  r|j<                  d   n|j>                  jA                  d      F }}tC        d |D              }|s~t)        j*                  d|       dz   d d!t-        j.                         v st)        j0                  |      rt)        j2                  |      nd!iz  }t5        t)        j6                  |            y# 1 sw Y   xY w# 1 sw Y   xY w# 1 sw Y   xY wc c}w )"uD   stale task 존재 시 send_telegram_alert가 호출되어야 한다.Nr   r   r  r   i rH   rL   rM   r   r   zstale-task-999r   r   r   r   r.  Tr/  r0  r   r1  uG   stale 태스크 존재인데 send_telegram_alert가 호출되지 않음r   r2  r<   r   r3  c              3   ^   K   | ]%  }|d uxr dt        |      j                         v  ' y w)Nstaler6  r8  s     r5   r;  zJTestCmdScanIntegration.test_cmd_scan_checks_stale_tasks.<locals>.<genexpr>X  r<  r=  u3   stale 관련 alert_type으로 호출되지 않음: r>  r   stale_alert_called)"r   r   r   r
   r,  rZ   rI   rO   r[   r\   r]   r^   r   r   r   r   r   r   r   r   r#   r)   r%   r&   r'   r(   r*   r+   r@  r   rA  rB  r   rC  )r-   r.   r   rD  r/   r  r   r   r   r2  r2   r@   r@  rE  rF  rL  rG  s                    r5    test_cmd_scan_checks_stale_tasksz7TestCmdScanIntegration.test_cmd_scan_checks_stale_tasks/  s{   &KK^_$KK\]$$X{; K(+>>MM'')335!

 	tzz,7'J ##H$5$5$9$9:X//a88CCE&#)
 	 !,,TZZ8H-IT[,\?@ 	J&*J#CRTU GH J	   k kk"kkkkkkkzkkkzkkk kkkkkk#22iwxaes499~'9tyy|t{{|?\\xx  eYd ee!ff%XYdXe#fffffff!fff!fffff!  	 	 ysC   L,L"L-L5L,4A	L9LLL)	$L,,L6N)rx   ry   rz   r{   r,  rH  rM  r|   r7   r5   r&  r&    s    R
> gD*gr7   r&  )+r{   builtinsr%   _pytest.assertion.rewrite	assertionrewriter#   rZ   r]   ossystempfilepathlibr   unittest.mockr   r   r   WORKSPACE_ROOTpathinsertorchestrator.auto_orchr   r   r   orchestrator.token_ledgerr	   r
   r   ImportErrorr   r   r   r   r   r   joinr   r~   r   r   r  r&  r|   r7   r5   <module>r^     s[      	 
   * 
 '!HHOOA~&
 
 2
	" 	&  sp spvTa TaxXp Xp@*U *Ud&t &t\ng ng{    "!#!"  &ww||N4YZww||N4MN!%&s6   3C <
C 
C. 	CCC+*C+.?D0/D0