
    hi,s                       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 d dlmZm	Z	m
Z
 ej                  j                  d ej                  j                  ej                  j                  e      d             d dlZd dl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 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& G d- d.      Z' G d/ d0      Z( G d1 d2      Z) G d3 d4      Z* G d5 d6      Z+ G d7 d8      Z, G d9 d:      Z- G d; d<      Z. G d= d>      Z/ G d? d@      Z0 G dA dB      Z1 G dC dD      Z2 G dE dF      Z3 G dG dH      Z4 G dI dJ      Z5 G dK dL      Z6 G dM dN      Z7 G dO dP      Z8 G dQ dR      Z9 G dS dT      Z:y)U    N)datetime)Path)	AsyncMockMockpatchz..)ChatMessageConversationMemoryc                       e Zd ZdZd Zy)TestAddAndGetMessagesuM   test_add_and_get_messages - 메시지 추가 후 get_context로 조회 가능c                    t               }d}|j                  |ddd       |j                  |ddd       |j                  |      }t        |      d	k(  sJ |d
   j                  dk(  sJ |d
   j
                  dk(  sJ |d
   j                  du sJ |d   j                  dk(  sJ |d   j
                  dk(  sJ |d   j                  du sJ y )N      제이회장님   안녕하세요Fis_bot	   잼민이u   안녕하세요!T   r   )r	   add_messageget_contextlensendertextr   selfmemchat_idmessagess       M/home/jay/workspace/services/multimodel-bot/tests/test_conversation_memory.pytest_add_and_get_messagesz/TestAddAndGetMessages.test_add_and_get_messages   s     "!24EeT.@N??7+8}!!!{!!%6666{#4444{!!U***{!![000{#5555{!!T)))    N)__name__
__module____qualname____doc__r    r    r   r   r      s
    W*r    r   c                       e Zd ZdZd Zy)TestRingBufferEvictionuY   test_ring_buffer_eviction - max_messages(20) 초과 시 오래된 메시지 자동 제거c                    t        d      }d}t        d      D ]  }|j                  |dd| d        |j                  |d	      }t	        |      dk(  sJ |d
   j
                  dk(  sJ |d   j
                  dk(  sJ y )N   max_messagesr      r   
   메시지 Fr   limitr      메시지 5u   메시지 24r	   ranger   r   r   r   )r   r   r   ir   s        r   test_ring_buffer_evictionz0TestRingBufferEviction.test_ring_buffer_eviction&   s     b1r 	XAOOG%6*QC8HQVOW	X ??7"?58}"""{=000|  N222r    N)r!   r"   r#   r$   r5   r%   r    r   r'   r'   #   s
    c3r    r'   c                       e Zd ZdZd Zy)TestMultiChatIsolationuK   test_multi_chat_isolation - 서로 다른 chat_id의 메시지가 격리됨c                    t               }d}d}|j                  |ddd       |j                  |ddd	       |j                  |      }|j                  |      }t        |      d
k(  sJ |d   j                  dk(  sJ t        |      d
k(  sJ |d   j                  dk(  sJ |d   j                  |d   j                  k7  sJ y )Nd      r      채팅A 메시지Fr   r      채팅B 메시지Tr   r   r	   r   r   r   r   )r   r   chat_achat_bmsgs_amsgs_bs         r   test_multi_chat_isolationz0TestMultiChatIsolation.test_multi_chat_isolation7   s     " 13FuU-@N((6{aay~~!44446{aay~~!4444 ay~~///r    N)r!   r"   r#   r$   rB   r%   r    r   r7   r7   4   s
    U0r    r7   c                       e Zd ZdZd Zd Zy)TestGetContextWithLimituI   test_get_context_with_limit - limit 파라미터로 최근 N개만 조회c                    t               }d}t        d      D ]  }|j                  |dd| d        |j                  |d      }t	        |      dk(  sJ |d	   j
                  d
k(  sJ |d   j
                  dk(  sJ y )N   
   r   r-   Fr      r.   r   r0   r1      메시지 9r2   )r   r   r   r4   recent_5s        r   test_get_context_with_limitz3TestGetContextWithLimit.test_get_context_with_limitO   s     "r 	XAOOG%6*QC8HQVOW	X ??7!?48}!!!{=000|  M111r    c                     t               }d}|j                  |ddd       |j                  |ddd       |j                  |d	
      }t        |      dk(  sJ y )N   r      하나Fr   r      둘Tr9   r.   r   )r	   r   r   r   r   s       r   )test_get_context_limit_larger_than_storedzATestGetContextWithLimit.test_get_context_limit_larger_than_stored[   s\     "!2HUKeDA??7#?68}!!!r    N)r!   r"   r#   r$   rK   rP   r%   r    r   rD   rD   L   s    S
2"r    rD   c                       e Zd ZdZd Zy)TestFormatContextBasicuH   test_format_context_basic - format_context가 올바른 문자열 생성c                     t               }d}|j                  |ddd       |j                  |ddd       |j                  |d	d
      }d|v sJ d|v sJ d|v sJ d|v sJ d|v sJ y )NrH   r   u*   데이터 기반 접근이 중요합니다Tr   r   u   그래서 결론이 뭐야?Fgemini_view_botu   현재 질문입니다   [이전 대화]u5   잼민이: 데이터 기반 접근이 중요합니다u,   제이회장님: 그래서 결론이 뭐야?u   [현재 질문/발언]u'   제이회장님: 현재 질문입니다r	   r   format_contextr   r   r   results       r   test_format_context_basicz0TestFormatContextBasic.test_format_context_basici   s     ".Zcgh!24QZ_`##G->@XY F***F&PPP=GGG'61118FBBBr    N)r!   r"   r#   r$   rZ   r%   r    r   rR   rR   f   s    RCr    rR   c                   (    e Zd ZdZd Zd Zd Zd Zy)TestFormatContextWithPersonauc   test_format_context_with_persona - format_context에 봇 이름과 페르소나 프롬프트 포함c                 Z    t               }d}|j                  |dd      }d|v sJ d|v sJ y )N   rT   u   테스트 질문u   너는 잼민이다U   이전 발언들을 참고하되 중복되지 않는 새로운 관점을 제시해라r	   rW   rX   s       r   "test_format_context_gemini_personaz?TestFormatContextWithPersona.test_format_context_gemini_persona|   sB     "##G->@RS$...fjppppr    c                 Z    t               }d}|j                  |dd      }d|v sJ d|v sJ y )N   codex_view_botu   코드 질문u   너는 코덱스다r_   r`   rX   s       r   !test_format_context_codex_personaz>TestFormatContextWithPersona.test_format_context_codex_persona   sA     "##G-=O$...fjppppr    c                 Z    t               }d}|j                  |dd      }d|v sJ d|v sJ y )N   claude_view_botu   균형 잡힌 분석u   너는 클로디다r_   r`   rX   s       r   "test_format_context_claude_personaz?TestFormatContextWithPersona.test_format_context_claude_persona   sB     "##G->@VW$...fjppppr    c                 N    t               }d}|j                  |dd      }d|v sJ y)u=   알 수 없는 봇 이름은 bot_username을 그대로 사용	   unknown_bot   질문r_   Nr`   rX   s       r   test_format_context_unknown_botz<TestFormatContextWithPersona.test_format_context_unknown_bot   s2     "##G]HEfjppppr    N)r!   r"   r#   r$   ra   re   ri   rn   r%   r    r   r\   r\   y   s    mqqqqr    r\   c                   "    e Zd ZdZd Zd Zd Zy)TestEmptyContextu`   test_empty_context - 빈 채팅의 format_context는 빈 문자열이 아닌 최소 프롬프트c                 n    t               }d}|j                  |d      }|dk7  sJ t        |      dkD  sJ y )NrG   rT    r   )r	   rW   r   rX   s       r   #test_empty_context_not_empty_stringz4TestEmptyContext.test_empty_context_not_empty_string   s>     "##G->?||6{Qr    c                 L    t               }d}|j                  |d      }d|v sJ y )N   rT   r_   r`   rX   s       r   #test_empty_context_contains_personaz4TestEmptyContext.test_empty_context_contains_persona   s1     "##G->?fjppppr    c                 L    t               }d}|j                  |d      }d|vsJ y)u>   메시지가 없으면 [이전 대화] 섹션이 없어야 함   rT   rU   Nr`   rX   s       r   2test_empty_context_no_previous_conversation_headerzCTestEmptyContext.test_empty_context_no_previous_conversation_header   s0     "##G->? ...r    N)r!   r"   r#   r$   rs   rv   ry   r%   r    r   rp   rp      s    jq/r    rp   c                       e Zd ZdZd Zd Zy)TestMessageOrderinguB   test_message_ordering - 메시지가 시간 순서대로 반환됨c                 4   t               }d}|j                  |ddd       |j                  |ddd       |j                  |d	d
d       |j                  |      }|d   j                  dk(  sJ |d   j                  dk(  sJ |d   j                  d
k(  sJ y )N   r   u
   첫 번째Fr   r   u
   두 번째T	   코덱스u
   세 번째r   r   r   )r	   r   r   r   r   s       r   test_message_orderingz)TestMessageOrdering.test_message_ordering   s     "!2LOl4Hl4H??7+{<///{<///{<///r    c                 <   t               }d}|j                  |ddd       |j                  |ddd       |j                  |d	d
d       |j                  |      }t        t	        |      dz
        D ]&  }||   j
                  ||dz      j
                  k  r&J  y )N   r   AFr   r   BTr~   Cr   )r	   r   r   r3   r   	timestamp)r   r   r   r   r4   s        r   test_message_timestamps_orderedz3TestMessageOrdering.test_message_timestamps_ordered   s     "!2CFc$?c$???7+s8}q() 	FAA;((HQUO,E,EEEE	Fr    N)r!   r"   r#   r$   r   r   r%   r    r   r{   r{      s    L0Fr    r{   c                       e Zd ZdZd Zd Zy)TestDefaultMaxMessagesu8   test_default_max_messages - 기본값이 50임을 확인c                 :    t               }|j                  dk(  sJ y )N2   r	   _max_messagesr   r   s     r   test_default_max_messagesz0TestDefaultMaxMessages.test_default_max_messages   s     "  B&&&r    c                     t               }d}t        d      D ]  }|j                  |dd| d        |j                  |d      }t	        |      d	k(  sJ y )
N   7   r   r-   Fr   r9   r.   r   r	   r3   r   r   r   r   r   r   r4   all_messagess        r   "test_default_max_messages_enforcedz9TestDefaultMaxMessages.test_default_max_messages_enforced   sh     "r 	XAOOG%6*QC8HQVOW	X wc:< B&&&r    N)r!   r"   r#   r$   r   r   r%   r    r   r   r      s    B''r    r   c                   "    e Zd ZdZd Zd Zd Zy)TestCustomMaxMessagesu?   test_custom_max_messages - 커스텀 max_messages 동작 확인c                    t        d      }d}t        d      D ]  }|j                  |dd| d        |j                  |d	
      }t	        |      dk(  sJ |d   j
                  dk(  sJ |d   j
                  dk(  sJ y )NrH   r*      rG   r   r-   Fr   r9   r.   r   r0   r1   rI   r2   r   s        r   test_custom_max_messages_5z0TestCustomMaxMessages.test_custom_max_messages_5   s     a0r 	XAOOG%6*QC8HQVOW	X wc:< A%%%A##}444B$$555r    c                 ^   t        d      }d}|j                  |ddd       |j                  |dd	d
       |j                  |ddd
       |j                  |ddd       |j                  |d      }t        |      dk(  sJ |d   j                  d	k(  sJ |d   j                  dk(  sJ y )NrF   r*      r   rN   Fr   r   rO   Tr~   u   셋u   넷r9   r.   r   r1   r=   )r   r   r   r   s       r   test_custom_max_messages_3z0TestCustomMaxMessages.test_custom_max_messages_3   s     a0!2HUKeDAeDA!2E%Hwc:< A%%%A##u,,,B$$---r    c                 >    t        d      }|j                  dk(  sJ y )Nrc   r*   r   r   s     r   )test_custom_max_messages_stored_correctlyz?TestCustomMaxMessages.test_custom_max_messages_stored_correctly  s      a0  A%%%r    N)r!   r"   r#   r$   r   r   r   r%   r    r   r   r      s    I
6.&r    r   c                   .    e Zd ZdZd Zd Zd Zd Zd Zy)TestPersistenceu$   JSONL 파일 저장/로드 테스트c                     t        t        |            }d}|j                  |ddd       t        j                         j                  d      }|| dz  }|j                         s
J d	|        y
)uH   add_message 호출 시 날짜별 JSONL 파일이 생성되어야 한다.storage_basei  r      테스트 메시지Fr   %Y-%m-%d.jsonlu(   JSONL 파일이 생성되어야 한다: Nr	   strr   r   nowstrftimeexistsr   tmp_pathr   r   today
jsonl_paths         r   #test_add_message_creates_jsonl_filez3TestPersistence.test_add_message_creates_jsonl_file  sr     c(m<!24IRWX''
35' 00
  "[&Nzl$[["r    c                 X   t        t        |            }d}|j                  |ddd       |j                  |ddd	       t        j                         j                  d
      }|| dz  }|j                  d      j                         j                         }t        |      dk(  sJ y)uK   add_message 호출 시 JSONL 파일에 메시지가 추가되어야 한다.r   i  r   r   Tr   r   u   반갑습니다Fr   r   utf-8encodingr   N)
r	   r   r   r   r   r   	read_textstrip
splitlinesr   )r   r   r   r   r   r   liness          r   #test_add_message_appends_jsonl_linez3TestPersistence.test_add_message_appends_jsonl_line%  s     c(m<.?M!24EeT''
35' 00
$$g$6<<>IIK5zQr    c                 v   t        t        |            }d}|j                  |ddd       t        j                         j                  d      }|| dz  }|j                  d	
      j                         }t        j                  |      }|d   dk(  sJ |d   dk(  sJ |d   du sJ |d   |k(  sJ d|v sJ y)u:   JSONL 각 라인이 올바른 JSON 형식이어야 한다.r   i  r   u   데이터 분석 중Tr   r   r   r   r   r   r   r   r   r   N
r	   r   r   r   r   r   r   r   jsonloadsr   r   r   r   r   r   linedatas           r   test_jsonl_line_formatz&TestPersistence.test_jsonl_line_format2  s     c(m<.DTR''
35' 00
##W#5;;=zz$H~,,,F|5555H~%%%I')))d"""r    c                    t        d      }d}|j                  |ddd       t        j                         j	                  d      }t        d	      | d
z  }|j                  |      }t        |      dk(  sJ |d   j                  dk(  sJ y)uR   storage_base=None이면 파일을 생성하지 않는다 (인메모리만 동작).Nr   i  r   u   파일 없음Fr   r   z$/home/jay/workspace/memory/groupchatr   r   r   )	r	   r   r   r   r   r   r   r   r   )r   r   r   r   r   default_pathr   s          r   &test_storage_base_none_no_file_createdz6TestPersistence.test_storage_base_none_no_file_createdD  s     d3!2OER ''
3BCvFVV ??7+8}!!!{?222r    c                    t        t        |            }d}d}|j                  |ddd       |j                  |dd	d
       t        j                         j                  d      }|| dz  }|j                  d      j                         j                         }t        |      dk(  sJ |D ch c]  }t        j                  |      d    }	}||	v sJ ||	v sJ yc c}w )uD   여러 chat_id의 메시지가 같은 날짜 파일에 저장된다.r   i  i  r   u   채팅AFr   r   u   채팅BTr   r   r   r   r   r   N)r	   r   r   r   r   r   r   r   r   r   r   r   )
r   r   r   r>   r?   r   r   r   lchat_ids_in_files
             r   #test_multiple_chat_ids_in_same_filez3TestPersistence.test_multiple_chat_ids_in_same_fileT  s     c(m< 19UKYtD''
35' 00
$$g$6<<>IIK5zQ>CDDJJqM)4DD)))))))) Es   1CN)	r!   r"   r#   r$   r   r   r   r   r   r%   r    r   r   r     s    .	\#$3 *r    r   c                   .    e Zd ZdZd Zd Zd Zd Zd Zy)TestLoadTodayu    load_today() 메서드 테스트c                 d   t        t        |            }d}|j                  |ddd       |j                  |ddd	       t        t        |            }|j                  |       |j	                  |      }t        |      d
k(  sJ |d   j                  dk(  sJ |d   j                  dk(  sJ y)uF   오늘 JSONL 파일에서 해당 chat_id의 메시지를 로드한다.r   i  r   u   첫 번째 메시지Fr   r   u   두 번째 메시지Tr   r   r   Nr	   r   r   
load_todayr   r   r   )r   r   mem1r   mem2r   s         r   !test_load_today_restores_messagesz/TestLoadToday.test_load_today_restores_messagesj  s     "s8}="35KTYZ+/EdS "s8}= ##G,8}!!!{#9999{#9999r    c                     t        t        |            }d}|j                  |       |j                  |      }|g k(  sJ y)u7   파일이 없으면 빈 deque로 시작한다 (정상).r   i  N)r	   r   r   r   r   r   r   r   r   s        r   #test_load_today_no_file_empty_dequez1TestLoadToday.test_load_today_no_file_empty_deque{  s=     c(m< 	w??7+2~~r    c                 @   t        t        |            }d}d}|j                  |ddd       |j                  |dd	d
       t        t        |            }|j                  |       |j	                  |      }t        |      dk(  sJ |d   j                  dk(  sJ y)u7   로드 시 해당 chat_id의 메시지만 가져온다.r   i  i  r   r;   Fr   r   r<   Tr   r   Nr   )r   r   r   r>   r?   r   r   s          r   "test_load_today_filters_by_chat_idz0TestLoadToday.test_load_today_filters_by_chat_id  s    !s8}=!24GPUV.A$O!s8}=##F+8}!!!{#6666r    c                 >   t        t        |      d      }d}t        d      D ]  }|j                  |dd| d        t        t        |      	      }|j	                  |       |j                  |d
      }t        |      dk(  sJ |d   j                  dk(  sJ y)u5   load_today는 최근 50개 메시지만 로드한다.r:   )r   r+   i  <   r   r-   Fr   r   r9   r.   r   r1   u   메시지 59N)r	   r   r3   r   r   r   r   r   )r   r   r   r   r4   r   r   s          r   test_load_today_limits_to_50z*TestLoadToday.test_load_today_limits_to_50  s    !s8}3Or 	YAW&7:aS9IRWX	Y "s8}= ##G3#78}"""|  N222r    c                 l   t        t        |            }d}|j                  |ddd       t        t        |            }|j                  |       |j	                  |      d   }|j
                  dk(  sJ |j                  dk(  sJ |j                  du sJ t        |j                  t              sJ y)	uB   로드된 메시지의 모든 필드가 올바르게 복원된다.r   i  r~   u   코드 리뷰 완료Tr   r   N)r	   r   r   r   r   r   r   r   
isinstancer   r   )r   r   r   r   r   msgs         r   'test_load_today_message_fields_restoredz5TestLoadToday.test_load_today_message_fields_restored  s    !s8}=+/EdS!s8}= w'*zz[(((xx1111zzT!!!#--222r    N)	r!   r"   r#   r$   r   r   r   r   r   r%   r    r   r   r   g  s    *:"7 3 3r    r   c                   :    e Zd ZdZd Zd Zd Zd Zd Zd Z	d Z
y	)
TestSummaryGenerationu   요약 생성 mock 테스트c                     t        t        |            }d}|j                  |ddd       |j                  |ddd	       |j                  j	                  |d
      dk(  sJ y)u/   chat_id별 메시지 카운트가 추적된다.r   iq  r   u
   메시지1Fr   r   u
   메시지2Tr   r   N)r	   r   r   _message_countgetr   r   r   r   s       r   #test_message_count_tracked_per_chatz9TestSummaryGeneration.test_message_count_tracked_per_chat  s_     c(m<!2LOl4H!!%%gq1Q666r    c                     t        t        |            }d}t        j                  |d      5 }t	        d      D ]  }|j                  |dd| d        |j                  sJ 	 d	d	d	       y	# 1 sw Y   y	xY w)
u7   50개 메시지가 쌓이면 요약이 트리거된다.r   ir  _generate_summaryr   r   r-   Fr   N)r	   r   r   objectr3   r   called)r   r   r   r   mock_summaryr4   s         r   %test_summary_triggered_at_50_messagesz;TestSummaryGeneration.test_summary_triggered_at_50_messages  s~     c(m<\\#23 	'|2Y \):j<LUZ[\  &&&&	' 	' 	's   7A//A8c                     t        t        |            dt        d      D ]  }j                  dd| d        fd}t	        j
                   |              y	)
u1   _generate_summary가 call_claude를 호출한다.r   is  rH   r   u   내용 Fr   c                     K   t        dt              5 } d| _        j                         d {    | j                  sJ | j
                  d   d   }d|v sJ 	 d d d        y 7 4# 1 sw Y   y xY ww)Nconversation_memory.call_claudenew_callableu   요약 결과입니다r   uB   아래 대화의 핵심 논점과 결론을 3~5줄로 요약해줘)r   r   return_valuer   r   	call_argsmock_clauder   r   r   s     r   runzJTestSummaryGeneration.test_generate_summary_calls_call_claude.<locals>.run  s     8yQ iU`+C(++G444"))))'11!4Q7	[_hhhhi i4i is,   A4A(A&*A(	A4&A((A1-A4Nr	   r   r3   r   asyncior   r   r   r4   r   r   r   s       @@r   'test_generate_summary_calls_call_claudez=TestSummaryGeneration.test_generate_summary_calls_call_claude  s\     c(m<q 	UAOOG%6'!eOT	U	i 	CEr    c                     t        t                    dj                  ddd       j                  ddd       fd	}t        j                   |              y
)uL   _generate_summary가 summaries/ 디렉토리에 JSON 파일을 저장한다.r   it  r      AI 기술 동향Tr   r~   u   코드 품질 향상c                     K   t        dt              5 } d| _        j                         d {    d d d        dz  }|j	                         sJ t        |j                  d            }t        |      dk(  sJ y 7 N# 1 sw Y   MxY ww)Nr   r   u"   핵심 논점: AI와 코드 품질	summaries*.jsonr   )r   r   r   r   r   listglobr   r   summaries_dir
json_filesr   r   r   s      r   r   zHTestSummaryGeneration.test_generate_summary_saves_json_file.<locals>.run  s     8yQ 5U`+O(++G4445 %{2M '')))m00:;Jz?a''' 55 5,   BBB BA
B BBBNr	   r   r   r   r   r   r   r   r   r   s    ` @@r   %test_generate_summary_saves_json_filez;TestSummaryGeneration.test_generate_summary_saves_json_file  sU     c(m<.@N.DTR	( 	CEr    c                     t        t                    dj                  ddd       j                  ddd	       fd
}t        j                   |              y)u>   저장된 요약 JSON 파일이 올바른 구조를 가진다.r   iu  r   u   주제 토론Tr   r   u   질문입니다Fc                    K   t        dt              5 } d| _        j                         d {    d d d        dz  }t	        |j                  d            d   }t        j                  |j                  d            }d	|v sJ d
|v sJ d|d
   v sJ d|d
   v sJ d|v sJ d|v sJ d|v sJ y 7 # 1 sw Y   xY ww)Nr   r   u   요약 텍스트r   r   r   r   r   r   message_rangefromtosummary
key_topicsparticipants	r   r   r   r   r   r   r   r   r   )r   r  	json_filer   r   r   r   s       r   r   zGTestSummaryGeneration.test_generate_summary_json_structure.<locals>.run  s     8yQ 5U`+=(++G4445 %{2M]//9:1=I::i1171CDD$&&&"d***T/222240000$$$4'''!T))) 55 5s,   CB8B6B8B C6B88C=CNr  r  s    ` @@r   $test_generate_summary_json_structurez:TestSummaryGeneration.test_generate_summary_json_structure  sU     c(m<odK!24EeT	*" 	CEr    c                     t        t        |            dj                  ddd       fd}t        j                   |              y)	u=   요약 생성 실패 시 예외를 발생시키지 않는다.r   iv  r   	   메시지Tr   c                     K   t        dt              5 } t        d      | _        j	                         d {    d d d        y 7 # 1 sw Y   y xY ww)Nr   r   
   API 오류)r   r   	Exceptionside_effectr   r   r   r   s    r   r   zOTestSummaryGeneration.test_generate_summary_failure_does_not_raise.<locals>.run  sU     8yQ 5U`*3L*A'++G4445 5 55 5s+   A%A
AA
	AA

AANr  r  s      @@r   ,test_generate_summary_failure_does_not_raisezBTestSummaryGeneration.test_generate_summary_failure_does_not_raise  s?     c(m<k$G	5 	CEr    c                     t        t        |            dj                  ddd       fd}t        j                   |              y)	u4   요약 생성마다 summary_counter가 증가한다.r   iw  r   u   주제 논의Tr   c                  Z  K   t        dt              5 } d| _        j                         d {    j                  j                  d      }j                         d {    j                  j                  d      }||dz   k(  sJ 	 d d d        y 7 i7 6# 1 sw Y   y xY ww)Nr   r   u   요약1r   r   )r   r   r   r   _summary_counterr   )r   count1count2r   r   s      r   r   zBTestSummaryGeneration.test_summary_counter_increments.<locals>.run&  s     8yQ ,U`+4(++G444--11'1=++G444--11'1=!+++, ,44	, ,s>   B+BB4B&B'*B	B+BBB($B+Nr  r  s      @@r   test_summary_counter_incrementsz5TestSummaryGeneration.test_summary_counter_increments  sA     c(m< 	odK	, 	CEr    N)r!   r"   r#   r$   r   r   r   r  r  r  r  r%   r    r   r   r     s(    &7
'$(6r    r   c                   4    e Zd ZdZd Zd Zd Zd Zd Zd Z	y)	TestFormatContextWithSummariesu&   요약 포함 format_context 테스트c                    t        t        |            }d}|dz  }|j                  d       t        j                         j                  d      }|| dz  }t        j                         j                         dd	d
dddgddgd}|j                  t        j                  |d      d       |j                  |ddd       |j                  |dd      }d|v sJ d|v sJ y)uC   요약이 있을 때 [이전 대화 요약] 섹션이 포함된다.r   iY  r   Tparentsr   	_001.jsonr   r   r
  r  u$   AI와 코드 품질에 대한 논의AIu   코드 품질r   r~   r   r	  r  r  r  Fensure_asciir   r      최근 메시지r   rT      현재 질문   [이전 대화 요약]Nr	   r   mkdirr   r   r   	isoformat
write_textr   dumpsr   rW   	r   r   r   r   r  r   summary_filesummary_datarY   s	            r   ,test_format_context_includes_summary_sectionzKTestFormatContextWithSummaries.test_format_context_includes_summary_section5  s     c(m< !;.D)''
3$%	'::!113&'r2=1(+6
 	

<e LW^_.@N##G->P'61115???r    c                     t        t        |            }d}|j                  |ddd       |j                  |dd      }d	|vsJ y
)u=   요약이 없을 때 [이전 대화 요약] 섹션이 없다.r   iZ  r   r  Tr   rT   rm   r-  N)r	   r   r   rW   )r   r   r   r   rY   s        r   8test_format_context_no_summary_section_when_no_summarieszWTestFormatContextWithSummaries.test_format_context_no_summary_section_when_no_summariesN  sL     c(m<k$G##G->I'v555r    c                    t        t        |            }d}|dz  }|j                  d       t        j                         j                  d      }|| dz  }t        j                         j                         dd	d
ddgdgd}|j                  t        j                  |d      d       |j                  |ddd       |j                  |dd      }d|v sJ y)u<   요약이 있을 때 [최근 대화] 섹션도 포함된다.r   i[  r   Tr#  r   r%  r   r   r&  u   테스트 요약	   테스트r   r(  Fr)  r   r   r   r+  r   rT   r,  u   [최근 대화]Nr.  r3  s	            r   /test_format_context_recent_conversation_sectionzNTestFormatContextWithSummaries.test_format_context_recent_conversation_sectionX  s     c(m< ;.D)''
3$%	'::!113&'r2)&-./
 	

<e LW^_.@N##G->P F***r    c                 r    t        t        |            }|j                         }t        |t              sJ y)u2   get_recent_summaries가 리스트를 반환한다.r   N)r	   r   get_recent_summariesr   r   )r   r   r   rY   s       r   &test_get_recent_summaries_returns_listzETestFormatContextWithSummaries.test_get_recent_summaries_returns_listo  s.     c(m<))+&$'''r    c                    t        t        |            }|dz  }|j                  d       t        j                         j                  d      }t        dd      D ]u  }|| d|d	d
z  }t        j                         j                         |dz
  dz  dz   |dz  dd| d| gdgd}|j                  t        j                  |d      d       w |j                  d      }t        |      dk(  sJ y)u=   get_recent_summaries가 오늘 요약 파일들을 읽는다.r   r   Tr#  r   r   rM   _03d.jsonr   r&     요약    주제r   r(  Fr)  r   r   rF   r.   Nr	   r   r/  r   r   r   r3   r0  r1  r   r2  r=  r   	r   r   r   r  r   r4   r4  r5  r   s	            r   +test_get_recent_summaries_reads_today_fileszJTestFormatContextWithSummaries.test_get_recent_summaries_reads_today_filesv  s    c(m< ;.D)''
3q! 		dA(eWAaWE+BBL%\\^557+,q5B,*:!b&!I$QC=!'s|n!2 3L ##DJJ|%$P[b#c		d ,,1,5	9~"""r    c                    t        t        |            }|dz  }|j                  d       t        j                         j                  d      }t        dd      D ]d  }|| d|d	d
z  }t        j                         j                         dddd| g g d}|j                  t        j                  |d      d       f |j                  d      }t        |      dk(  sJ y)u;   get_recent_summaries의 limit 파라미터가 동작한다.r   r   Tr#  r   r   r^   r@  rA  rB  r   r&  rC  r(  Fr)  r   r   r   r.   NrE  rF  s	            r   test_get_recent_summaries_limitz>TestFormatContextWithSummaries.test_get_recent_summaries_limit  s     c(m< ;.D)''
3q! 		dA(eWAaWE+BBL%\\^557*+2!6$QC=  "L ##DJJ|%$P[b#c		d ,,1,5	9~"""r    N)
r!   r"   r#   r$   r6  r8  r;  r>  rG  rI  r%   r    r   r!  r!  2  s$    0@26+.(#,#r    r!  c                   (    e Zd ZdZd Zd Zd Zd Zy)TestGenerateInsightu   insight 생성 mock 테스트c                     t        t        |            dj                  ddd       fd}t        j                   |              y)	u.   generate_insight가 문자열을 반환한다.r   iA  r   u   오늘 논의 내용Fr   c                     K   t        dt              5 } d| _        j                         d {   }t	        |t
              sJ t        |      dkD  sJ 	 d d d        y 7 0# 1 sw Y   y xY ww)Nr   r   u4   핵심 논점: ...
결론: ...
액션 아이템: ...r   )r   r   r   generate_insightr   r   r   r   rY   r   r   s     r   r   zETestGenerateInsight.test_generate_insight_returns_string.<locals>.run  sm     8yQ 'U`+c("33G<<!&#...6{Q&	' '<' 's,   A0A$A"&A$	A0"A$$A-)A0Nr  r  s      @@r   $test_generate_insight_returns_stringz8TestGenerateInsight.test_generate_insight_returns_string  sB     c(m<!24JSXY	' 	CEr    c                     t        t                    dj                  ddd       fd}t        j                   |              y)	uI   generate_insight가 insights/ 디렉토리에 .md 파일을 저장한다.r   iB  r   u   주요 논의Tr   c                     K   t        dt              5 } d| _        j                         d {    d d d        dz  }|j	                         sJ t        |j                  d            }t        |      dk(  sJ y 7 N# 1 sw Y   MxY ww)Nr   r   u   인사이트 내용insights*.mdr   )r   r   r   rN  r   r   r   r   )r   insights_dirmd_filesr   r   r   s      r   r   zDTestGenerateInsight.test_generate_insight_saves_md_file.<locals>.run  s     8yQ 4U`+@(**73334 $j0L&&(((L--f56Hx=A%%% 44 4r  Nr  r  s    ` @@r   #test_generate_insight_saves_md_filez7TestGenerateInsight.test_generate_insight_saves_md_file  s?     c(m<odK	& 	CEr    c                     t        t                    dj                  ddd       fd}t        j                   |              y)	uE   generate_insight 파일명이 YYYY-MM-DD_insight_NNN.md 형식이다.r   iC  r~   u   코드 리뷰Tr   c                  P  K   t        dt              5 } d| _        j                         d {    d d d        dz  }t	        |j                  d            }t        j                         j                  d      t        fd|D              sJ y 7 e# 1 sw Y   dxY ww)Nr   r   u   인사이트rS  rT  r   c              3   Z   K   | ]"  }|j                   j                   d        $ yw)	_insight_N)name
startswith).0fr   s     r   	<genexpr>z\TestGenerateInsight.test_generate_insight_md_filename_format.<locals>.run.<locals>.<genexpr>  s'     P!qvv((E7))<=Ps   (+)
r   r   r   rN  r   r   r   r   r   any)r   rU  rV  r   r   r   r   s      @r   r   zITestGenerateInsight.test_generate_insight_md_filename_format.<locals>.run  s     8yQ 4U`+9(**73334 $j0LL--f56HLLN++J7EPxPPPP 44 4s,   B&BBBA!B&BB#B&Nr  r  s    ` @@r   (test_generate_insight_md_filename_formatz<TestGenerateInsight.test_generate_insight_md_filename_format  s@     c(m<odK	Q 	CEr    c                     t        t        |            dj                  ddd       fd}t        j                   |              y)	uc   generate_insight가 call_claude를 핵심 논점/결론/액션 아이템 요청으로 호출한다.r   iD  r      논의 내용Fr   c                  .  K   t        dt              5 } d| _        j                         d {    | j                  sJ | j
                  d   d   j                         }t        fddD              sJ 	 d d d        y 7 T# 1 sw Y   y xY ww)Nr   r   u   핵심 논점과 결론r   c              3   &   K   | ]  }|v  
 y wNr%   )r^  keywordr   s     r   r`  zoTestGenerateInsight.test_generate_insight_calls_call_claude_with_summary_prompt.<locals>.run.<locals>.<genexpr>  s     yG7i/ys   )u   핵심 논점u   결론u   액션 아이템   요약)r   r   r   rN  r   r   lowerra  )r   prompt_lowerr   r   r   s     @r   r   z\TestGenerateInsight.test_generate_insight_calls_call_claude_with_summary_prompt.<locals>.run  s     8yQ zU`+D(**7333"))))'11!4Q7	(0y?xyyyyz z3z zs-   BB	BA
B	>	BB		BBNr  r  s      @@r   ;test_generate_insight_calls_call_claude_with_summary_promptzOTestGenerateInsight.test_generate_insight_calls_call_claude_with_summary_prompt  sA     c(m<!2OER	z 	CEr    N)r!   r"   r#   r$   rP  rW  rb  rl  r%   r    r   rK  rK    s    ' &&r    rK  c                   "    e Zd ZdZd Zd Zd Zy)TestGracefulDegradationu7   파일 저장 실패 시 graceful degradation 테스트c                     t        t        |            }d}t        dt        d            5  |j	                  |ddd	       d
d
d
       y
# 1 sw Y   y
xY w)u:   파일 저장 실패 시 예외가 발생하지 않는다.r   i)#  builtins.open   디스크 꽉 참r  r   u   파일 실패 테스트Fr   N)r	   r   r   OSErrorr   r   s       r   &test_file_write_failure_does_not_raisez>TestGracefulDegradation.test_file_write_failure_does_not_raise  sU     c(m<?8K0LM 	aOOG%68QZ_O`	a 	a 	as   AAc                    t        t        |            }d}t        dt        d            5  |j	                  |ddd	       d
d
d
       |j                  |      }t        |      dk(  sJ |d   j                  dk(  sJ y
# 1 sw Y   ?xY w)u9   파일 저장 실패 시 인메모리에는 저장된다.r   i*#  rp  rq  rr  r   u   메모리 저장 확인Fr   Nr   r   )r	   r   r   rs  r   r   r   r   r   s        r   .test_file_write_failure_still_stores_in_memoryzFTestGracefulDegradation.test_file_write_failure_still_stores_in_memory  s     c(m<?8K0LM 	aOOG%68QZ_O`	a ??7+8}!!!{#<<<<	a 	as   BBc                    t        j                         j                  d      }|| dz  }t        j                  ddt        j                         j                         dddd	      }d
}|j                  | d| dd       t        t        |            }|j                  d       |j                  d      }t        |      dk(  sJ |d   j                  dk(  sJ y)u0   load_today 시 손상된 라인은 건너뛴다.r   r   r   u   정상 메시지Ti+#  )r   r   r   r   r   Fr)  u   { 손상된 JSON }
r   r   r   r   r   N)r   r   r   r   r2  r0  r1  r	   r   r   r   r   r   )r   r   r   r   
valid_linecorrupt_liner   r   s           r   $test_load_today_corrupt_line_skippedz<TestGracefulDegradation.test_load_today_corrupt_line_skipped  s    ''
35' 00
 ZZ%*%\\^557 	

 ,B|nB?'R c(m<t??4(8}!!!{#5555r    N)r!   r"   r#   r$   rt  rv  r{  r%   r    r   rn  rn    s    Aa
=6r    rn  c                   4    e Zd ZdZd Zd Zd Zd Zd Zd Z	y)	TestInactivityTimeru1   30분 무활동 자동 요약 트리거 테스트c                     t        t        |            }d}|j                  |ddd       ||j                  v sJ t	        |j                  |   t
              sJ y)u6   add_message 호출 시 _last_activity가 갱신된다.r   i'  r   r:  Fr   N)r	   r   r   _last_activityr   r   r   s       r   )test_last_activity_updated_on_add_messagez=TestInactivityTimer.test_last_activity_updated_on_add_message&  sY     c(m<!2KN#,,,,,#,,W5x@@@r    c                 l    t               }t        |d      sJ t        |j                  t              sJ y)u2   _inactivity_tasks 딕셔너리가 초기화된다._inactivity_tasksN)r	   hasattrr   r  dictr   s     r   !test_inactivity_tasks_dict_existsz5TestInactivityTimer.test_inactivity_tasks_dict_exists.  s1     "s/000#//666r    c                     t        t        |            dt        d      D ]  }j                  dd| d        fd}t	        j
                   |              y	)
u(   무활동 시 요약이 트리거된다.r   i'  r^   r   r-   Fr   c                    K   t        j                  d      5 } t        dt              5  j                         d {    | j	                         d d d        d d d        y 7 &# 1 sw Y   xY w# 1 sw Y   y xY wwN_schedule_summary!conversation_memory.asyncio.sleepr   )r   r   r   _check_inactivityassert_called_once_withr   r   r   s    r   r   z\TestInactivityTimer.test_check_inactivity_triggers_summary_when_no_new_messages.<locals>.run<  s     c#67 B<>YW B//888 88ABB B 9B BB BsE   BA6A*A(A*A6	B(A**A3	/A66A?;BNr   r   s       @@r   ;test_check_inactivity_triggers_summary_when_no_new_messageszOTestInactivityTimer.test_check_inactivity_triggers_summary_when_no_new_messages4  s^     c(m<q 	XAOOG%6*QC8HQVOW	X	B 	CEr    c                     t        t        |            dt        d      D ]  }j                  dd| d        fd}t	        j
                   |              y	)
u?   새 메시지가 있으면 요약을 트리거하지 않는다.r   i'  r^   r   r-   Fr   c                  
  K   t        j                  d      5 } fd}t        d|      5  j                         d {    | j                          d d d        d d d        y 7 %# 1 sw Y   xY w# 1 sw Y   y xY ww)Nr  c                 P   K   t        j                         j                  <   y wrg  )r   r   r  )secondsr   r   s    r   
fake_sleepziTestInactivityTimer.test_check_inactivity_skips_when_new_message_arrived.<locals>.run.<locals>.fake_sleepO  s     2:,,.C&&w/s   #&r  rr  )r   r   r  assert_not_called)r   r  r   r   s     r   r   zUTestInactivityTimer.test_check_inactivity_skips_when_new_message_arrived.<locals>.runL  s     c#67 5<A >JW 5//888 22455 5 95 55 5sE   BA7A+A)A+A7 	B)A++A4	0A77B <BNr   r   s       @@r   4test_check_inactivity_skips_when_new_message_arrivedzHTestInactivityTimer.test_check_inactivity_skips_when_new_message_arrivedE  s]     c(m<q 	XAOOG%6*QC8HQVOW	X		5 	CEr    c                     t        t        |            dj                  ddd       j                  ddd	       fd
}t        j                   |              y)uC   메시지가 5개 미만이면 요약을 트리거하지 않는다.r   i'  r   rN   Fr   r   rO   Tc                    K   t        j                  d      5 } t        dt              5  j                         d {    | j	                          d d d        d d d        y 7 %# 1 sw Y   xY w# 1 sw Y   y xY wwr  )r   r   r   r  r  r  s    r   r   zNTestInactivityTimer.test_check_inactivity_skips_when_few_messages.<locals>.run`  su     c#67 5<>YW 5//888 22455 585 55 5sE   BA5A)A'A)A5	B'A))A2	.A55A>:BNr  r  s      @@r   -test_check_inactivity_skips_when_few_messageszATestInactivityTimer.test_check_inactivity_skips_when_few_messagesY  sT     c(m<!2HUKeDA	5 	CEr    c                     t        t        |            }d}t               }d|j                  _        ||j
                  |<   |j                  |ddd       |j                  j                          y)u9   새 메시지가 오면 기존 타이머가 취소된다.r   i'  Fr   u   새 메시지r   N)	r	   r   r   doner   r  r   cancelassert_called_once)r   r   r   r   	mock_tasks        r   ,test_existing_timer_cancelled_on_new_messagez@TestInactivityTimer.test_existing_timer_cancelled_on_new_messageh  sa     c(m< F	&+	#)2g&!2OER++-r    N)
r!   r"   r#   r$   r  r  r  r  r  r  r%   r    r   r}  r}  #  s$    ;A7"(.r    r}  c                   "    e Zd ZdZd Zd Zd Zy)TestKeyTopicsExtractionu"   key_topics 자동 추출 테스트c                     t        t                    dj                  ddd       fd}t        j                   |              y)	u@   call_claude가 JSON 응답을 주면 key_topics가 채워진다.r   i*  r   r   Tr   c                    K   t        j                  dg ddd      } t        dt              5 }| |_        j                         d {    d d d        dz  }t        |j                  d	            }t        |      d
k(  sJ t        j                  |d   j                  d            }|d   g dk(  sJ y 7 p# 1 sw Y   oxY ww)Nu!   AI 기술 동향에 대한 논의)r'  u   기술u   동향r  r  Fr)  r   r   r   r   r   r   r   r   r  )r   r2  r   r   r   r   r   r   r   r   r   )json_responser   r  r  r   r   r   r   s        r   r   zQTestKeyTopicsExtraction.test_key_topics_populated_from_json_response.<locals>.run  s      JJB"< #M 8yQ 5U`+8(++G4445 %{2Mm00:;Jz?a'''::jm55w5GHD%)CCCC 55 5s/   .CC B>C A,C>C  C	CNr  r  s    ` @@r   ,test_key_topics_populated_from_json_responsezDTestKeyTopicsExtraction.test_key_topics_populated_from_json_responsey  sA     c(m<.@N	D& 	CEr    c                     t        t                    dj                  ddd       fd}t        j                   |              y)	uI   call_claude가 일반 텍스트를 주면 key_topics는 빈 배열이다.r   i*  r   u   일반 대화Tr   c                  T  K   t        dt              5 } d| _        j                         d {    d d d        dz  }t	        |j                  d            }t        j                  |d   j                  d            }|d	   g k(  sJ |d
   dk(  sJ y 7 h# 1 sw Y   gxY ww)Nr   r   u    그냥 요약 텍스트입니다r   r   r   r   r   r  r  r  r   r  r  r   r   r   r   s       r   r   zOTestKeyTopicsExtraction.test_key_topics_empty_on_non_json_response.<locals>.run  s     8yQ 5U`+M(++G4445 %{2Mm00:;J::jm55w5GHD%+++	?&HHHH 55 5s,   B(BBBA$B(BB%!B(Nr  r  s    ` @@r   *test_key_topics_empty_on_non_json_responsezBTestKeyTopicsExtraction.test_key_topics_empty_on_non_json_response  s@     c(m<odK		I 	CEr    c                     t        t        |            dj                  ddd       fd}t        j                   |              y)	uA   _generate_summary의 프롬프트가 JSON 형식을 요청한다.r   i*  r   u   토론 내용Fr   c                     K   t        dt              5 } d| _        j                         d {    | j                  d   d   }d|v sd|v sJ d|v sd|v sJ d d d        y 7 3# 1 sw Y   y xY ww)	Nr   r   u/   {"summary": "요약", "key_topics": ["주제"]}r   r  u	   키워드JSONr   )r   r   r   r   r   r   s     r   r   zOTestKeyTopicsExtraction.test_generate_summary_prompt_requests_json.<locals>.run  s     8yQ BU`+\(++G444'11!4Q7	#y0K94LLL*f	.AAAB B4B Bs,   A3A'A%*A'	A3%A''A0,A3Nr  r  s      @@r   *test_generate_summary_prompt_requests_jsonzBTestKeyTopicsExtraction.test_generate_summary_prompt_requests_json  sA     c(m<!2OER	B 	CEr    N)r!   r"   r#   r$   r  r  r  r%   r    r   r  r  v  s    ,6&r    r  c                       e Zd ZdZd Zy)TestInsightDMSendingu   insight DM 전송 테스트c                      y)u`   cleanup 시 _send_insight_to_owner가 호출되는지 확인은 main_bot 통합 테스트에서.Nr%   )r   r   s     r   ,test_send_insight_to_owner_called_on_cleanupzATestInsightDMSending.test_send_insight_to_owner_called_on_cleanup  s     	r    N)r!   r"   r#   r$   r  r%   r    r   r  r    s
    %r    r  c                   (    e Zd ZdZd Zd Zd Zd Zy)TestInsightEventu)   insight 이벤트 파일 생성 테스트c                     t        t        |            dj                  ddd       fd}t        j                   |              y)	u;   generate_insight 후 _create_insight_event가 호출된다.r   i.  r   rd  Fr   c                    K   t        dt              5 } d| _        t        j                  d      5 }j	                         d {    |j                          d d d        d d d        y 7 %# 1 sw Y   xY w# 1 sw Y   y xY ww)Nr   r      인사이트 결과_create_insight_event)r   r   r   r   rN  r  )r   
mock_eventr   r   s     r   r   zFTestInsightEvent.test_generate_insight_calls_create_event.<locals>.run  s     8yQ 4U`+@(\\#'>? 4:..w77711344 4 84 44 4sE   BA<A0A.	A0A<%	B.A00A9	5A<<BBNr  r  s      @@r   (test_generate_insight_calls_create_eventz9TestInsightEvent.test_generate_insight_calls_create_event  s@     c(m<!2OER	4 	CEr    c                     t        t        |            }|dz  }|j                  |       t        |j	                  d            }t        |      dk(  sJ y)u:   _create_insight_event가 이벤트 파일을 생성한다.r   events
events_dirzgroupchat-insight-*.eventr   N)r	   r   r  r   r   r   )r   r   r   r  event_filess        r   &test_create_insight_event_creates_filez7TestInsightEvent.test_create_insight_event_creates_file  sR     c(m<(
!!Z!8:??+FGH;1$$$r    c                    t        t        |            }|dz  }|j                  |       t        |j	                  d            }t        j                  |d   j                  d            }|d   d	k(  sJ d
|v sJ d|v sJ y)u7   이벤트 파일이 올바른 JSON 구조를 가진다.r   r  r  z*.eventr   r   r   typezgroupchat-insightr   rU  N)r	   r   r  r   r   r   r   r   )r   r   r   r  r  r   s         r   (test_create_insight_event_json_structurez9TestInsightEvent.test_create_insight_event_json_structure  s     c(m<(
!!Z!8:??956zz+a.22G2DEF|2222d"""%%%r    c                 d    t        t        |            }|j                  t        d             y)uD   이벤트 파일 생성 실패 시 예외가 발생하지 않는다.r   z/nonexistent/readonly/pathr  N)r	   r   r  r   )r   r   r   s      r   *test_create_insight_event_failure_no_raisez;TestInsightEvent.test_create_insight_event_failure_no_raise  s'     c(m<!!T2N-O!Pr    N)r!   r"   r#   r$   r  r  r  r  r%   r    r   r  r    s    3%&Qr    r  c                       e Zd ZdZd Zy)TestSummaryCounterRecoveryud   _summary_counter 복구 테스트 - 재시작 시 기존 파일 수를 확인해 번호 이어받기c                    t        t        |            d|dz  j                  d       t        j                         j                  d      t        dd      D ]:  } d	|d
dz  }|j                  t        j                  d| g d      d       < j                  ddd       fd}t        j                   |              y)uX   기존 요약 파일 3개 존재 시, 새 요약이 004번으로 생성되는지 확인.r   i!N  r   Tr#  r   r   rM   	_general_rA  rB  ri  r  r   r   r   u
   새 논의r   c            
      \  K   t        dt              5 } t        j                  ddgdg g ddd	      | _        j                         d {    d d d        t        j                   d
            }t        |      dk(  sJ |d   }d|j                  v sJ y 7 O# 1 sw Y   NxY ww)Nr   r   u
   새 요약rD  generalexploratoryr  r  	topic_tagkey_decisionsaction_itemsconsensus_levelFr)  z_*.jsonrM   r1   _004)
r   r   r   r2  r   r   sortedr   r   r\  )r   r  new_filer   r   r  r   s      r   r   zQTestSummaryCounterRecovery.test_counter_recovery_from_existing_files.<locals>.run  s     8yQ 5U`+/::#/'/j%.)+(*+8 "'
,( ++G4445   2 2eWG3D EFJz?a'''!"~HX]]*** 55 5s/   B,9B BB AB,B  B)%B,N)r	   r   r/  r   r   r   r3   r1  r   r2  r   r   r   )	r   r   r4   fpr   r   r   r  r   s	        @@@@r   )test_counter_recovery_from_existing_fileszDTestSummaryCounterRecovery.test_counter_recovery_from_existing_files  s     c(m< !;.D)''
3q! 	AE7)Ac7%!@@BMM

s|2FG   	 	l4H	+, 	CEr    N)r!   r"   r#   r$   r  r%   r    r   r  r    s
    n)r    r  c                   (    e Zd ZdZd Zd Zd Zd Zy)TestPIIMaskinguJ   PII 마스킹 테스트 - JSONL에는 마스킹, 인메모리에는 원본c                 D   t        t        |            }d}d}|j                  |d|d       t        j                         j                  d      }|| dz  }|j                  d	
      j                         }t        j                  |      }d|d   v sJ d|d   vsJ y)uN   전화번호(010-1234-5678 → [전화번호])가 JSONL에서 마스킹된다.r   i	R  u%   제 번호는 010-1234-5678입니다.r   Fr   r   r   r   r   u   [전화번호]r   z010-1234-5678Nr   	r   r   r   r   original_textr   r   r   r   s	            r   !test_phone_number_masked_in_jsonlz0TestPIIMasking.test_phone_number_masked_in_jsonl)  s     c(m<?!2M%P''
35' 00
##W#5;;=zz$4<///d6l222r    c                 D   t        t        |            }d}d}|j                  |d|d       t        j                         j                  d      }|| dz  }|j                  d	
      j                         }t        j                  |      }d|d   v sJ d|d   vsJ y)uO   주민번호(900101-1234567 → [주민번호])가 JSONL에서 마스킹된다.r   i
R  u(   주민번호는 900101-1234567입니다.r   Fr   r   r   r   r   u   [주민번호]r   z900101-1234567Nr   r  s	            r   $test_resident_number_masked_in_jsonlz3TestPIIMasking.test_resident_number_masked_in_jsonl9       c(m<B!2M%P''
35' 00
##W#5;;=zz$4<///tF|333r    c                 D   t        t        |            }d}d}|j                  |d|d       t        j                         j                  d      }|| dz  }|j                  d	
      j                         }t        j                  |      }d|d   v sJ d|d   vsJ y)uO   계좌번호(110-123-456789 → [계좌번호])가 JSONL에서 마스킹된다.r   iR  u(   계좌번호는 110-123-456789입니다.r   Fr   r   r   r   r   u   [계좌번호]r   z110-123-456789Nr   r  s	            r   #test_account_number_masked_in_jsonlz2TestPIIMasking.test_account_number_masked_in_jsonlI  r  r    c                     t        t        |            }d}d}|j                  |d|d       |j                  |      }t	        |      dk(  sJ |d   j
                  |k(  sJ d	|d   j
                  v sJ y
)u:   인메모리 deque에는 원본 텍스트가 저장된다.r   iR  u(   연락처 010-9999-8888로 전화해줘.r   Fr   r   r   z010-9999-8888N)r	   r   r   r   r   r   )r   r   r   r   r  r   s         r   !test_original_preserved_in_memoryz0TestPIIMasking.test_original_preserved_in_memoryY  s     c(m<B!2M%P??7+8}!!!{=000(1+"2"2222r    N)r!   r"   r#   r$   r  r  r  r  r%   r    r   r  r  &  s    T3 4 4 3r    r  c                   "    e Zd ZdZd Zd Zd Zy)TestTopicTagu   topic_tag 필드 포함 확인c                 (   t        t        |            }d}|j                  |ddd       t        j                         j                  d      }|| dz  }|j                  d	
      j                         }t        j                  |      }d|v sJ y)u4   JSONL 레코드에 topic_tag 필드가 포함된다.r   iU  r   u   오늘 날씨가 좋네요Tr   r   r   r   r   r  Nr   r   s           r   test_jsonl_record_has_topic_tagz,TestTopicTag.test_jsonl_record_has_topic_tagk  s     c(m<.JSWX''
35' 00
##W#5;;=zz$d"""r    c                 0   t        t        |            }d}|j                  |ddd       t        j                         j                  d      }|| dz  }|j                  d	
      j                         }t        j                  |      }|d   dk(  sJ y)u*   topic_tag의 기본값이 'general'이다.r   iU  r   u   일반 메시지Tr   r   r   r   r   r  r  Nr   r   s           r   'test_topic_tag_default_value_is_generalz4TestTopicTag.test_topic_tag_default_value_is_generaly  s     c(m<.@N''
35' 00
##W#5;;=zz$K I---r    c                 l    t               }t        |d      sJ t        |j                  t              sJ y)u/   _current_topic 딕셔너리가 초기화된다._current_topicN)r	   r  r   r  r  r   s     r   )test_current_topic_initialized_to_generalz6TestTopicTag.test_current_topic_initialized_to_general  s1     "s,---#,,d333r    N)r!   r"   r#   r$   r  r  r  r%   r    r   r  r  h  s    (#.4r    r  c                   .    e Zd ZdZd Zd Zd Zd Zd Zy)TestTopicChangeDetectionu   주제 전환 감지 테스트c                     t        t        |            }d}|j                  |ddd       t        ddt	        j
                         d      }|j                  ||      }|d	u sJ y
)uC   전환 키워드('다른 주제')로 주제 전환이 감지된다.r   iY  r      일반 이야기Fr      다른 주제로 넘어가죠r   r   r   r   TNr	   r   r   r   r   r   _detect_topic_changer   r   r   r   r   rY   s         r   !test_keyword_detects_topic_changez:TestTopicChangeDetection.test_keyword_detects_topic_change  si     c(m< 	!24FuU$0lln	
 ))'37~~r    c                     t        t        |            }d}|j                  |ddd       t        ddt	        j
                         d	
      }|j                  ||      }|d	u sJ y)uF   전환 키워드('그건 그렇고')로 주제 전환이 감지된다.r   iY  r   r  Fr   r   u%   그건 그렇고 오늘 회의는요?Tr  Nr  r  s         r   )test_keyword_geugeon_detects_topic_changezBTestTopicChangeDetection.test_keyword_geugeon_detects_topic_change  sg     c(m<!24FuU8lln	
 ))'37~~r    c                    ddl m} t        t        |            }d}t        j                          |d      z
  |j
                  |<   t        ddt        j                         d	
      }|j                  ||      }|du sJ y)u<   침묵 갭(5분 초과)으로 주제 전환이 감지된다.r   	timedeltar   iY  ih  r  r   u   새로운 이야기Fr  TNr   r  r	   r   r   r  r   r  r   r   r  r   r   r   rY   s          r   %test_silence_gap_detects_topic_changez>TestTopicChangeDetection.test_silence_gap_detects_topic_change  sv    & c(m< '/llny7M&M7#$&lln	
 ))'37~~r    c                    ddl m} t        t        |            }d}t        j                          |d      z
  |j
                  |<   t        ddt        j                         d	
      }|j                  ||      }|du sJ y)uB   일반 메시지에서는 주제 전환이 감지되지 않는다.r   r  r   iY  r   r  r   u   계속 이야기해요Tr  FNr  r  s          r   #test_normal_message_no_topic_changez<TestTopicChangeDetection.test_normal_message_no_topic_change  sv    & c(m< '/llny7L&L7#)lln	
 ))'37r    c                     t        t        |            }d}|j                  |ddd       |j                  |ddd       |j                  j	                  |      dk(  sJ y	)
uH   주제 전환 감지 시 _current_topic이 'pending'으로 변경된다.r   iY  r   r  Fr   r  pendingN)r	   r   r   r  r   r   s       r   test_topic_change_sets_pendingz7TestTopicChangeDetection.test_topic_change_sets_pending  sa     c(m<!24FuU!24S\ab!!%%g.);;;r    N)	r!   r"   r#   r$   r  r  r  r   r  r%   r    r   r  r    s    (" &&<r    r  c                   4    e Zd ZdZd Zd Zd Zd Zd Zd Z	y)	TestSummaryMetadataSchemau'   요약 메타데이터 스키마 확인c                     t        t                    dj                  ddd       fd}t        j                   |              y)	u>   저장된 요약 JSON에 key_decisions 필드가 존재한다.r   i]  r   u   결정 사항 토론Tr   c            
        K   t        dt              5 } t        j                  ddgddgdgdd	d
      | _        j                         d {    d d d        dz  }t        |j                  d            }t        j                  |d   j                  d            }d|v sJ |d   dgk(  sJ y 7 e# 1 sw Y   dxY ww)Nr   r   ri  rD  meetingu   결정1u   액션1agreedr  Fr)  r   r   r   r   r   r  
r   r   r   r2  r   r   r   r   r   r   r  s       r   r   zETestSummaryMetadataSchema.test_summary_has_key_decisions.<locals>.run  s     8yQ 5U`+/::#+'/j%.*3)2+3 "'
,( ++G4445 %{2Mm00:;J::jm55w5GHD"d***(YK777 55 5s/   C;B8B6B8A!C6B88C=CNr  r  s    ` @@r   test_summary_has_key_decisionsz8TestSummaryMetadataSchema.test_summary_has_key_decisions  s@     c(m<.DTR	8, 	CEr    c                     t        t                    dj                  ddd       fd}t        j                   |              y)	u=   저장된 요약 JSON에 action_items 필드가 존재한다.r   i]  r   u   액션 아이템 논의Tr   c            
        K   t        dt              5 } t        j                  dg dg ddgddd	
      | _        j                         d {    d d d        dz  }t        |j                  d            }t        j                  |d   j                  d            }d|v sJ |d   ddgk(  sJ y 7 f# 1 sw Y   exY ww)Nr   r   ri  actionu   할 일1u   할 일2	tentativer  Fr)  r   r   r   r   r   r  r
  r  s       r   r   zDTestSummaryMetadataSchema.test_summary_has_action_items.<locals>.run
  s     8yQ 5U`+/::#+&(%-)+)3Z(@+6 "'
,( ++G4445 %{2Mm00:;J::jm55w5GHD!T)))'J
+CCCC 55 5s/   C:B8B6B8A"C6B88C=CNr  r  s    ` @@r   test_summary_has_action_itemsz7TestSummaryMetadataSchema.test_summary_has_action_items  sB     c(m<.GPTU	D, 	CEr    c                     t        t                    dj                  ddd       fd}t        j                   |              y)	u@   저장된 요약 JSON에 consensus_level 필드가 존재한다.r   i]  r~   u   합의 수준 테스트Tr   c            
        K   t        dt              5 } t        j                  dg dg g ddd      | _        j                         d {    d d d        d	z  }t        |j                  d
            }t        j                  |d   j                  d            }d|v sJ |d   dk(  sJ y 7 d# 1 sw Y   cxY ww)Nr   r   ri  	consensusdecidedr  Fr)  r   r   r   r   r   r  r
  r  s       r   r   zGTestSummaryMetadataSchema.test_summary_has_consensus_level.<locals>.run(  s     8yQ 5U`+/::#+&(%0)+(*+4 "'
,( ++G4445 %{2Mm00:;J::jm55w5GHD$,,,)*i777 55 5s/   C 8B4B2B4A C 2B44B=9C Nr  r  s    ` @@r    test_summary_has_consensus_levelz:TestSummaryMetadataSchema.test_summary_has_consensus_level"  sA     c(m<.GPTU	8, 	CEr    c                     t        t                    dj                  ddd       fd}t        j                   |              y)	u.   요약 파일명에 topic_tag가 포함된다.r   i]  r   u   주제 파일명 테스트Tr   c            
      Z  K   t        dt              5 } t        j                  dg dg g ddd      | _        j                         d {    d d d        d	z  }t        |j                  d
            }t        |      dk(  sJ d|d   j                  v sJ y 7 O# 1 sw Y   NxY ww)Nr   r   ri  ai_techr  r  Fr)  r   r   r   r   )
r   r   r   r2  r   r   r   r   r   r\  r   s      r   r   zPTestSummaryMetadataSchema.test_summary_filename_includes_topic_slug.<locals>.runF  s     8yQ 5U`+/::#+&(%.)+(*+8 "'
,( ++G4445 %{2Mm00:;Jz?a'''
1 2 2222 55 5s/   B+8BBBAB+BB($B+Nr  r  s    ` @@r   )test_summary_filename_includes_topic_slugzCTestSummaryMetadataSchema.test_summary_filename_includes_topic_slug@  sA     c(m<.JSWX	3* 	CEr    c                     t        t                    dj                  ddd       fd}t        j                   |              y)	u5   저장된 요약 JSON에 date 필드가 존재한다.r   i]  r   u   날짜 필드 확인Tr   c            
        K   t        dt              5 } t        j                  dg dg g ddd      | _        j                         d {    d d d        d	z  }t        |j                  d
            }t        j                  |d   j                  d            }d|v sJ t        j                         j                  d      }|d   |k(  sJ y 7 # 1 sw Y   xY ww)Nr   r   ri  r  r  r  Fr)  r   r   r   r   r   dater   )r   r   r   r2  r   r   r   r   r   r   r   r   r   )r   r  r  r   r   r   r   r   s        r   r   zBTestSummaryMetadataSchema.test_summary_has_date_field.<locals>.runc  s     8yQ 5U`+/::#+&(%.)+(*+8 "'
,( ++G4445 %{2Mm00:;J::jm55w5GHDT>!>LLN++J7E<5((( 55 5s/   C#8CCCBC#CC C#Nr  r  s    ` @@r   test_summary_has_date_fieldz5TestSummaryMetadataSchema.test_summary_has_date_field]  s@     c(m<.DTR	). 	CEr    c                     t        t                    dj                  ddd       fd}t        j                   |              y)	uQ   JSON 파싱 실패(fallback) 시 key_decisions, action_items가 빈 배열이다.r   i]  r   u   fallback 테스트Tr   c                  h  K   t        dt              5 } d| _        j                         d {    d d d        dz  }t	        |j                  d            }t        j                  |d   j                  d            }|d	   g k(  sJ |d
   g k(  sJ |d   dk(  sJ y 7 r# 1 sw Y   qxY ww)Nr   r   u'   일반 텍스트 응답 (파싱 불가)r   r   r   r   r   r  r  r  r  r  r  s       r   r   zMTestSummaryMetadataSchema.test_summary_fallback_has_empty_fields.<locals>.run  s     8yQ 5U`+T(++G4445 %{2Mm00:;J::jm55w5GHD(B...'2---)*m;;; 55 5s,   B2B&B$B&A.B2$B&&B/+B2Nr  r  s    ` @@r   &test_summary_fallback_has_empty_fieldsz@TestSummaryMetadataSchema.test_summary_fallback_has_empty_fields|  s@     c(m<.B4P	< 	CEr    N)
r!   r"   r#   r$   r  r  r  r  r  r   r%   r    r   r  r    s#    1<<<:>r    r  c                       e Zd ZdZd Zd Zy)TestXMLTagSeparationu   XML 태그 분리 확인c                     t        t        |            dj                  ddd       fd}t        j                   |              y)	uH   _generate_summary 프롬프트에 <user_content> 태그가 포함된다.r   ia  r   u   XML 태그 테스트Fr   c            
        K   t        dt              5 } t        j                  dg dg g ddd      | _        j                         d {    | j                  d	   d	   }d
|v sJ d|v sJ 	 d d d        y 7 ,# 1 sw Y   y xY ww)Nr   r   ri  r  r  r  Fr)  r   <user_content></user_content>)r   r   r   r2  r   r   r   r   s     r   r   zWTestXMLTagSeparation.test_generate_summary_prompt_has_xml_user_content_tag.<locals>.run  s     8yQ 6U`+/::#+&(%.)+(*+8 "'
,( ++G444'11!4Q7	'9444(I5556 6 56 6s.   B8A<A:"A<1	B:A<<BBNr  r  s      @@r   5test_generate_summary_prompt_has_xml_user_content_tagzJTestXMLTagSeparation.test_generate_summary_prompt_has_xml_user_content_tag  sB     c(m<!24JSXY	6$ 	CEr    c                     t        t        |            dj                  ddd       fd}t        j                   |              y)	uG   generate_insight 프롬프트에 <user_content> 태그가 포함된다.r   ia  r   u!   인사이트 XML 태그 테스트Fr   c                     K   t        dt              5 } d| _        j                         d {    | j                  d   d   }d|v sJ d|v sJ 	 d d d        y 7 ,# 1 sw Y   y xY ww)Nr   r   r  r   r%  r&  )r   r   r   rN  r   r   s     r   r   zWTestXMLTagSeparation.test_generate_insight_prompt_has_xml_user_content_tag.<locals>.run  s     8yQ 6U`+@(**7333'11!4Q7	'9444(I5556 636 6s,   A,A A"A 	A,A  A)%A,Nr  r  s      @@r   5test_generate_insight_prompt_has_xml_user_content_tagzJTestXMLTagSeparation.test_generate_insight_prompt_has_xml_user_content_tag  sB     c(m<!24W`ef	6 	CEr    N)r!   r"   r#   r$   r'  r*  r%   r    r   r"  r"    s    "4r    r"  c                       e Zd ZdZd Zd Zy)TestFilePermissions   파일 퍼미션 테스트c                     t        t        |            }d}|j                  |ddd       t        t	        j
                  |      j                        dd }|d	k(  sJ y)
u/   JSONL 디렉토리가 0o700으로 설정된다.r   ie  r   u   퍼미션 테스트Fr   N700)r	   r   r   octosstatst_mode)r   r   r   r   dir_modes        r   'test_jsonl_directory_has_700_permissionz;TestFilePermissions.test_jsonl_directory_has_700_permission  s\     c(m<!24IRWX rwwx(001"#65   r    c                 :   t        t        |            }d}|j                  |ddd       t        j                         j                  d      }|| dz  }|j                         sJ t        t        j                  |      j                        d	d
 }|dk(  sJ y
)u)   JSONL 파일이 0o600으로 설정된다.r   ie  r   r-  Fr   r   r   r/  N600)r	   r   r   r   r   r   r   r1  r2  r3  r4  )r   r   r   r   r   r   	file_modes          r   "test_jsonl_file_has_600_permissionz6TestFilePermissions.test_jsonl_file_has_600_permission  s     c(m<!24PY^_''
35' 00
  """
+334RS9	E!!!r    N)r!   r"   r#   r$   r6  r:  r%   r    r   r,  r,    s    $	!"r    r,  c                   (    e Zd ZdZd Zd Zd Zd Zy)TestSummaryLocku2   요약 중복 실행 방지 세마포어 테스트c                 l    t               }t        |d      sJ t        |j                  t              sJ y)u=   _summary_lock 딕셔너리가 __init__에서 초기화된다._summary_lockN)r	   r  r   r>  r  r   s     r    test_summary_lock_exists_in_initz0TestSummaryLock.test_summary_lock_exists_in_init  s0     "sO,,,#++T222r    c                     t        t        |            dj                  ddd       g fd}t        j                   |              y)	uB   _generate_summary 실행 중 _summary_lock[chat_id]가 True이다.r   i1u  r   r   Tr   c                    K   t               } fd}|| _        t        d|       5  j                         d {    d d d        d   du sJ j                  j                        du sJ y 7 5# 1 sw Y   4xY ww)Nc           	         K   j                  j                  j                  d             t        j                  dg dg g dd      S w)NFri  r  r  r  )appendr>  r   r   r2  )promptr   lock_during_executionr   s    r   capture_lockzYTestSummaryLock.test_summary_lock_set_during_execution.<locals>.run.<locals>.capture_lock  sP     %,,S->->-B-B7E-RSzz#+&(%.)+(*+8	 	s   AAr   r   TF)r   r  r   r   r>  r   )original_call_clauderF  r   rE  r   s     r   r   zCTestSummaryLock.test_summary_lock_set_during_execution.<locals>.run  s     #,;  0< ,8:NO 5++G4445 )+t333$$((1U::: 55 5s,   %BA5A3A51B3A55A>:BNr  )r   r   r   r   rE  r   s      @@@r   &test_summary_lock_set_during_executionz6TestSummaryLock.test_summary_lock_set_during_execution  sF     c(m<.CDQ "	;4 	CEr    c                     t        t        |            dj                  ddd       dj                  <   fd}t	        j
                   |              y)	u:   lock이 걸려있으면 _generate_summary가 스킵된다.r   i2u  r   r   Tr   c                     K   t        dt              5 } j                         d {    | j                          d d d        y 7 # 1 sw Y   y xY wwNr   r   r   r   r   r  r  s    r   r   z=TestSummaryLock.test_summary_skipped_when_locked.<locals>.run  sR     8yQ 0U`++G444--/0 040 0+   AA
AA
	AA

AAN)r	   r   r   r>  r   r   r  s      @@r    test_summary_skipped_when_lockedz0TestSummaryLock.test_summary_skipped_when_locked
  sR     c(m<.CDQ &*'"	0 	CEr    c                     t        t        |            dj                  ddd       fd}t        j                   |              y)	u4   요약 생성 실패 시에도 lock이 해제된다.r   i3u  r   r   Tr   c                     K   t        dt              5 } t        d      | _        j	                         d {    j
                  j                        du sJ 	 d d d        y 7 -# 1 sw Y   y xY ww)Nr   r   r  F)r   r   r  r  r   r>  r   r  s    r   r   zBTestSummaryLock.test_summary_lock_released_on_failure.<locals>.run!  sr     8yQ ?U`*3L*A'++G444((,,W5>>>	? ?4? ?s,   A6%A*A(#A*	A6(A**A3/A6Nr  r  s      @@r   %test_summary_lock_released_on_failurez5TestSummaryLock.test_summary_lock_released_on_failure  s@     c(m<.CDQ	? 	CEr    N)r!   r"   r#   r$   r?  rH  rN  rQ  r%   r    r   r<  r<    s    <3"H"r    r<  c                   .    e Zd ZdZd Zd Zd Zd Zd Zy)TestRateLimitu"   일일 LLM 호출 예산 테스트c                 l    t               }t        |d      sJ t        |j                  t              sJ y)u0   _llm_call_count 딕셔너리가 초기화된다._llm_call_countN)r	   r  r   rU  r  r   s     r   "test_llm_call_count_exists_in_initz0TestRateLimit.test_llm_call_count_exists_in_init.  s1     "s-...#--t444r    c                 V    t               }t        |d      sJ |j                  dk(  sJ y)u+   _daily_llm_budget의 기본값이 50이다._daily_llm_budgetr   N)r	   r  rX  r   s     r    test_daily_llm_budget_default_50z.TestRateLimit.test_daily_llm_budget_default_504  s/     "s/000$$***r    c                     t        t        |            dj                  ddd       fd}t        j                   |              y)	u2   요약 생성 시 _llm_call_count가 증가한다.r   iy  r   r:  Tr   c            
        K   t        dt              5 } t        j                  dg dg g dd      | _        t        j                         j                  d      }j                  j                  |d      }j                         d {    j                  j                  |d      }||d	z   k(  sJ 	 d d d        y 7 4# 1 sw Y   y xY ww)
Nr   r   ri  r  r  r  r   r   r   )r   r   r   r2  r   r   r   r   rU  r   r   )r   r   beforeafterr   r   s       r   r   zDTestRateLimit.test_llm_call_count_increments_on_summary.<locals>.run@  s     8yQ +U`+/::#+&(%.)+(*+8	,( !//
;,,00:++G444++//q9
***+ + 5+ +s/   CA5C
B?*C6	C?CC
CNr  r  s      @@r   )test_llm_call_count_increments_on_summaryz7TestRateLimit.test_llm_call_count_increments_on_summary:  s?     c(m<k$G	+$ 	CEr    c                    t        t        |            d_        dj                  ddd       t	        j
                         j                  d      }dj                  |<   fd	}t        j                   |              y
)u0   예산 초과 시 요약 생성이 스킵된다.r   rH   iy  r   r:  Tr   r   c                     K   t        dt              5 } j                         d {    | j                          d d d        y 7 # 1 sw Y   y xY wwrK  rL  r  s    r   r   zDTestRateLimit.test_summary_skipped_when_budget_exceeded.<locals>.run_  sR     8yQ 0U`++G444--/0 040 0rM  N
r	   r   rX  r   r   r   r   rU  r   r   r   r   r   r   r   r   s       @@r   )test_summary_skipped_when_budget_exceededz7TestRateLimit.test_summary_skipped_when_budget_exceededT  so     c(m< !k$G ''
3%&E"	0
 	CEr    c                    t        t        |            d_        dj                  ddd       t	        j
                         j                  d      }dj                  |<   fd	}t        j                   |              y
)uG   예산 초과 시 generate_insight가 에러 메시지를 반환한다.r   rF   iy  r   r:  Tr   r   c                  Z   K   j                         d {   } d| v sd| v sJ y y 7 w)N   예산   초과)rN  rY   r   r   s    r   r   zJTestRateLimit.test_insight_returns_error_when_budget_exceeded.<locals>.runp  s:     //88Fv%V);;;);% 9s   +)+Nra  rb  s       @@r   /test_insight_returns_error_when_budget_exceededz=TestRateLimit.test_insight_returns_error_when_budget_exceededf  sm     c(m< !k$G''
3%&E"	< 	CEr    N)	r!   r"   r#   r$   rV  rY  r^  rc  ri  r%   r    r   rS  rS  +  s    ,5+4$r    rS  c                   "    e Zd ZdZd Zd Zd Zy)TestDateTransitionBoundaryu6   날짜 전환 경계 시간순 역전 방지 테스트c                     t        t        |            }t        dddddd      }|j                  |      }|J d	t        |      v sJ y)
u9   _today_jsonl_path가 timestamp 파라미터를 받는다.r   i  rF   r      ;   )tsNz
2026-03-14)r	   r   r   _today_jsonl_path)r   r   r   ro  paths        r   'test_today_jsonl_path_accepts_timestampzBTestDateTransitionBoundary.test_today_jsonl_path_accepts_timestampz  sU     c(m<dAr2r2.$$$+s4y(((r    c                     t        t        |            }|j                         }t        j                         j                  d      }|J |t        |      v sJ y)u6   timestamp 미제공 시 현재 날짜를 사용한다.r   r   N)r	   r   rp  r   r   r   )r   r   r   rq  r   s        r   &test_today_jsonl_path_default_is_todayzATestDateTransitionBoundary.test_today_jsonl_path_default_is_today  sR     c(m<$$&''
3D	!!!r    c                     t        t        |            }d}|j                  |ddd       t        j                         j                  d      }|| dz  }|j                         sJ y	)
uI   메시지 timestamp 기준으로 올바른 날짜 파일에 기록된다.r   i}  r   u   오늘 메시지Fr   r   r   Nr   r   s         r   )test_message_written_to_correct_date_filezDTestDateTransitionBoundary.test_message_written_to_correct_date_file  sd     c(m<!24FuU''
35' 00
  """r    N)r!   r"   r#   r$   rr  rt  rv  r%   r    r   rk  rk  w  s    @)"	#r    rk  c                   "    e Zd ZdZd Zd Zd Zy)TestTopicConfirmationu9   pending topic이 요약 시 확정되는 로직 테스트c                     t        t        |            dj                  ddd       fd}t        j                   |              y)	u>   pending 상태의 topic이 _generate_summary 후 확정된다.r   i  r   u   다른 주제로 가죠Tr   c            
        K   t        dt              5 } t        j                  dg dg g dd      | _        dj
                  <   j                         d {    j
                     dk(  sJ 	 d d d        y 7 "# 1 sw Y   y xY ww)Nr   r   ri  r  r  r  r  )r   r   r   r2  r   r  r   r  s    r   r   zMTestTopicConfirmation.test_pending_topic_confirmed_after_summary.<locals>.run  s     8yQ @U`+/::#+&(%.)+(*+8	,( /8""7+++G444))'2i???@ @ 5@ @s/   BAA?A=A?4	B=A??BBNr  r  s      @@r   *test_pending_topic_confirmed_after_summaryz@TestTopicConfirmation.test_pending_topic_confirmed_after_summary  sB     c(m<.GPTU	@  	CEr    c                     t        t        |            dj                  ddd       dj                  <   fd}t	        j
                   |              y	)
uJ   pending이 아닌 topic은 _generate_summary에서 변경되지 않는다.r   i  r   u   이야기 계속Tr   existing_topicc            
         K   t        dt              5 } t        j                  dg dg g dd      | _        j                         d {    j                     dk(  sJ 	 d d d        y 7 "# 1 sw Y   y xY ww)Nr   r   ri  	new_topicr  r  r}  )r   r   r   r2  r   r   r  r  s    r   r   zETestTopicConfirmation.test_non_pending_topic_not_changed.<locals>.run  s     8yQ GU`+/::#+&(%0)+(*+8	,( ++G444))'26FFFFG G 5G Gs.   A<6A0A.A0%	A<.A00A95A<Nr	   r   r   r  r   r   r  s      @@r   "test_non_pending_topic_not_changedz8TestTopicConfirmation.test_non_pending_topic_not_changed  sQ     c(m<.@N&67#	G 	CEr    c                     t        t        |            dj                  ddd       dj                  <   fd}t	        j
                   |              y	)
uJ   LLM이 topic_tag를 주지 않으면 pending이 'general'로 확정된다.r   i  r   r:  Tr   r  c                     K   t        dt              5 } d| _        j                         d {    j                     dk(  sJ 	 d d d        y 7 "# 1 sw Y   y xY ww)Nr   r   u%   일반 텍스트 (JSON 파싱 실패)r  )r   r   r   r   r  r  s    r   r   zNTestTopicConfirmation.test_pending_topic_with_fallback_to_general.<locals>.run  sl     8yQ @U`+R(++G444))'2i???	@ @4@ @s,   A"AAA	A"AAA"Nr  r  s      @@r   +test_pending_topic_with_fallback_to_generalzATestTopicConfirmation.test_pending_topic_with_fallback_to_general  sP     c(m<k$G&/7#	@ 	CEr    N)r!   r"   r#   r$   r{  r  r  r%   r    r   rx  rx    s    C20r    rx  c                       e Zd ZdZd Zd Zy)TestGetAllSummaryFilesu)   get_all_summary_files 메서드 테스트c                     t        t        |            }|dz  }|j                  d       |j                         }t	        |t
              sJ y)u3   get_all_summary_files가 리스트를 반환한다.r   r   Tr#  N)r	   r   r/  get_all_summary_filesr   r   )r   r   r   r  rY   s        r   'test_get_all_summary_files_returns_listz>TestGetAllSummaryFiles.test_get_all_summary_files_returns_list  sI     c(m< ;.D)**,&$'''r    c                    t        t        |            }|dz  }|j                  d       t        j                         j                  d      }|| dz  }|j                  t        j                  d|dg g g d	d
      d       |j                         }t        |      dk(  sJ d|d   v sJ |d   d   | dk(  sJ y)u2   반환된 각 항목에 filename 필드가 있다.r   r   Tr#  r   z_general_001.jsonri  r  r  )r  r  r  r  r  r  r  r   r   r   filenamer   _general_001N)r	   r   r/  r   r   r   r1  r   r2  r  r   )r   r   r   r  r   r  rY   s          r   ,test_get_all_summary_files_includes_filenamezCTestGetAllSummaryFiles.test_get_all_summary_files_includes_filename  s     c(m< ;.D)''
3w&788
JJ'!!*"$%'$&'4
  	 	
 **,6{aVAY&&&ay$5'(>>>>r    N)r!   r"   r#   r$   r  r  r%   r    r   r  r    s    3(?r    r  c                   6    e Zd ZdZd	dZd Zd Zd Zd Zd Z	y)
TestSmartSearchu0   smart_search(query, chat_id) 메서드 테스트Nc           
      
   t        j                         j                  d      }||z  }t        j                         j                         ||||g|xs g g ddgd	}|j	                  t        j                  |d      d       |S )	u7   테스트용 요약 JSON 파일을 생성하는 헬퍼.r   r  r   	r   r  r  r  r  r  r  r  r  Fr)  r   r   )r   r   r   r0  r1  r   r2  )	r   r  r  r  r  r  r   r  r   s	            r   _make_summary_filez"TestSmartSearch._make_summary_file  s    ''
3X%!113"$+*0b,./

 	djjE:WM	r    c                    t        t        |            d|dz  }|j                  d       t        j                         j                  d      }| j                  || ddd	       fd
}t        j                   |              y)u*   smart_search가 문자열을 반환한다.r   iA  r   Tr#  r   _ai_tech_001.jsonu   AI 기술 동향 논의r  c                     K   t        dt              5 } d| _        j                  d       d {   }t	        |t
              sJ 	 d d d        y 7  # 1 sw Y   y xY ww)Nr   r   u!   AI 관련 검색 결과입니다.r'  )r   r   r   smart_searchr   r   rO  s     r   r   z=TestSmartSearch.test_smart_search_returns_string.<locals>.run-  s_     8yQ /U`+N("//g>>!&#.../ />/ /s,   A!AAA
	A!AAA!N	r	   r   r/  r   r   r   r  r   r   r   r   r  r   r   r   r   s        @@r    test_smart_search_returns_stringz0TestSmartSearch.test_smart_search_returns_string  s~     c(m< ;.D)''
3g&'%		
	/ 	CEr    c                    t        t        |            d|dz  }|j                  d       t        j                         j                  d      }| j                  || ddd	d
g       fd}t        j                   |              y)uQ   요약 파일에 키워드가 매칭되면 LLM 호출 후 결과를 반환한다.r   iB  r   Tr#  r   r  u(   AI 기술 동향에 대한 심층 논의r  u   AI 도입 결정)r  c                    K   t        dt              5 } d| _        j                  d       d {   }| j                  sJ t        |t              sJ t        |      dkD  sJ 	 d d d        y 7 ># 1 sw Y   y xY ww)Nr   r   u(   AI 기술 관련 내용이 있습니다.r'  r   )r   r   r   r  r   r   r   r   rO  s     r   r   zDTestSmartSearch.test_smart_search_with_matching_keyword.<locals>.runE  s~     8yQ 'U`+U("//g>>"))))!&#...6{Q&' '>' 's,   A?A3A14A3(	A?1A33A<8A?Nr  r  s        @@r   'test_smart_search_with_matching_keywordz7TestSmartSearch.test_smart_search_with_matching_keyword5  s     c(m< ;.D)''
3g&'6-. 	  	
	' 	CEr    c                    t        t        |            d|dz  }|j                  d       t        j                         j                  d      }| j                  || ddd	       fd
}t        j                   |              y)uV   매칭 결과가 없을 때 '검색 결과가 없습니다' 메시지를 반환한다.r   iC  r   Tr#  r   z_weather_001.jsonu   날씨 이야기weatherc                     K   t        dt              5 } j                  d       d {   }| j                          d|v sJ 	 d d d        y 7 $# 1 sw Y   y xY ww)Nr   r   u   블록체인u   검색 결과가 없습니다)r   r   r  r  rO  s     r   r   z9TestSmartSearch.test_smart_search_no_results.<locals>.run^  sf     8yQ AU`"//HH--/6&@@@	A AHA As,   AAAA	AAAANr  r  s        @@r   test_smart_search_no_resultsz,TestSmartSearch.test_smart_search_no_resultsO  s     c(m< ;.D)''
3g&'		
	A 	CEr    c                 D   t        t        |            d_        d|dz  }|j                  d       t	        j
                         j                  d      }| j                  || dd	d
       dj                  |<   fd}t        j                   |              y)u>   LLM 예산 초과 시 예산 초과 메시지를 반환한다.r   r   iD  r   Tr#  r   _ai_001.jsonu   AI 논의 내용aic                  \   K   j                  d       d {   } d| v sd| v sJ y y 7 w)Nr'  rf  rg  )r  rh  s    r   r   z9TestSmartSearch.test_smart_search_rate_limit.<locals>.runz  s<     ++D'::Fv%V);;;);% ;s   ,*,N)r	   r   rX  r/  r   r   r   r  rU  r   r   r  s        @@r   test_smart_search_rate_limitz,TestSmartSearch.test_smart_search_rate_limitg  s     c(m< ! ;.D)''
3g\"		
 &'E"	< 	CEr    c                    t        t        |            d|dz  }|j                  d       t        j                         j                  d      }| j                  || ddd	       fd
}t        j                   |              y)uN   LLM에 전달되는 프롬프트에 <user_content> XML 태그가 포함된다.r   iE  r   Tr#  r   r  u   AI 관련 대화r  c                     K   t        dt              5 } d| _        j                  d       d {    | j                  sJ | j
                  d   d   }d|v sJ d|v sJ 	 d d d        y 7 :# 1 sw Y   y xY ww)Nr   r   u   검색 결과r'  r   r%  r&  )r   r   r   r  r   r   r   s     r   r   zATestSmartSearch.test_smart_search_xml_tag_separation.<locals>.run  s     8yQ 6U`+:(&&tW555"))))'11!4Q7	'9444(I5556 656 6s,   A;A/A-0A/$	A;-A//A84A;Nr  r  s        @@r   $test_smart_search_xml_tag_separationz4TestSmartSearch.test_smart_search_xml_tag_separation  s}     c(m< ;.D)''
3g\"		
	6 	CEr    rg  )
r!   r"   r#   r$   r  r  r  r  r  r  r%   r    r   r  r  	  s#    :$.402r    r  c                   .    e Zd ZdZd Zd Zd Zd Zd Zy)TestDeferredIndexinguR   지연 인덱싱 — summaries/ 파일 200개 초과 시 _index.json 자동 생성c                 4   t        j                         j                  d      }t        |      D ]g  }|| d|ddz  }t        j                         j	                         |d| ddgg g ddgd		}|j                  t        j                  |d
      d       i y)u;   테스트용 요약 파일을 count개 생성하는 헬퍼.r   r  04drB  u   요약 내용 r  r  r   r  Fr)  r   r   N)r   r   r   r3   r0  r1  r   r2  )r   r  countr   r4   r  r   s          r   _create_summary_filesz*TestDeferredIndexing._create_summary_files  s    ''
3u 	RAE7)Ac7%!@@B%\\^557+A3/&(k!# "#0!2 3
D MM$**T>MQ	Rr    c                     t        t        |            }|dz  }|j                  d       | j                  |d       |j	                          |dz  }|j                         rJ y)uE   파일이 200개 이하일 때 _index.json이 생성되지 않는다.r   r   Tr#  r:   _index.jsonNr	   r   r/  r  r  r   r   r   r   r  
index_paths        r    test_index_not_created_under_200z5TestDeferredIndexing.test_index_not_created_under_200  sh     c(m< ;.D)""=#6 	!!#"]2
$$&&&&r    c                     t        t        |            }|dz  }|j                  d       | j                  |d       |j	                          |dz  }|j                         sJ d       y)	uJ   파일이 200개 초과(201개)일 때 _index.json이 자동 생성된다.r   r   Tr#     r  u7   _index.json이 201개 초과 시 생성되어야 한다Nr  r  s        r   test_index_created_over_200z0TestDeferredIndexing.test_index_created_over_200  sk     c(m< ;.D)""=#6 	!!#"]2
  "]$]]"r    c                    t        t        |            }|dz  }|j                  d       t        j                         j                  d      }| d|ddd	d
 dg}|dz  }|j                  t        j                  |d      d       |j                         }t        |t              sJ t        d |D              }|sJ d       y	)uP   _index.json이 존재할 때 get_all_summary_files가 인덱스를 사용한다.r   r   Tr#  r   _ai_001r  u   AI 관련 요약Nr   r  r  r  r  r  Fr)  r   r   c              3   D   K   | ]  }|j                  d       dk(    yw)r  r  N)r   )r^  items     r   r`  zMTestDeferredIndexing.test_get_all_summary_files_uses_index.<locals>.<genexpr>  s     EdDHH[)T1Es    u:   인덱스의 ai 항목이 결과에 포함되어야 한다)r	   r   r/  r   r   r   r1  r   r2  r  r   r   ra  )	r   r   r   r  r   
index_datar  rY   founds	            r   %test_get_all_summary_files_uses_indexz:TestDeferredIndexing.test_get_all_summary_files_uses_index  s     c(m< ;.D)''
3
  %gW-!-cr2	

 #]2
djj%HSZ[ **,&$'''EfEERRRur    c                    t        t        |            d|dz  }|j                  d       t        j                         j                  d      }| d|dd	d
g}|dz  j                  t        j                  |d      d       j                  ddd       fd}t        j                   |              y)u?   새 요약이 생성될 때 _index.json이 자동 갱신된다.r   i,  r   Tr#  r   _old_001	old_topic   이전 요약r  r  Fr)  r   r   r   u   새로운 논의 내용r   c            
      f  K   t        dt              5 } t        j                  ddgdg g ddd	      | _        j                         d {    d d d        t        j                  j                  d
            }|D cg c]  }|d   	 }}d|v sJ d       y 7 O# 1 sw Y   NxY wc c}w w)Nr   r   u   새로운 요약 내용u	   신주제r  r  r  Fr)  r   r   r  u>   새 요약의 topic_tag가 인덱스에 추가되어야 한다)r   r   r   r2  r   r   r   r   )r   updated_indexr  
new_topicsr   r  r   s       r   r   zCTestDeferredIndexing.test_index_updated_on_new_summary.<locals>.run   s     8yQ 5U`+/::#<'2m%0)+(*+8 "'
,( ++G4445 !JJz';';W';'MNM8EF${+FJF*,n.nn, 55 5  Gs:   B19B BB 1B1B,B1B  B)%B1N)r	   r   r/  r   r   r   r1  r   r2  r   r   r   )	r   r   r  r   r  r   r   r  r   s	         @@@r   !test_index_updated_on_new_summaryz6TestDeferredIndexing.test_index_updated_on_new_summary  s     c(m< ;.D)''
3
  %gX.(*	

 #]2
djj%HSZ[ 	.GPTU	o( 	CEr    N)	r!   r"   r#   r$   r  r  r  r  r  r%   r    r   r  r    s!    \R$'^S8,r    r  c                   (    e Zd ZdZd Zd Zd Zd Zy)TestJSONLRotationuB   JSONL rotation — 단일 파일 5만 줄 초과 시 자동 분할c                 r   t        t        |            }d}t        j                         j	                  d      }|| dz  }|j                  dd       t        j                  |dd	d
      5  |j                  |ddd       ddd       || dz  }|j                         rJ d       y# 1 sw Y   )xY w)ud   5만 줄 미만일 때 기존 파일에 계속 append하고 _part2 파일이 생성되지 않는다.r   i  r   r   rr   r   r   _get_jsonl_line_countiO  T)r   creater   u   마지막 메시지Fr   N_part2.jsonlu.   _part2.jsonl 파일이 생성되면 안 된다)
r	   r   r   r   r   r1  r   r   r   r   r   r   r   r   r   r   
part2_paths          r    test_rotation_under_50k_no_splitz2TestJSONLRotation.test_rotation_under_50k_no_split  s     c(m< ''
35' 00
b73\\#6USWX 	]OOG%68MV[O\	] 5' 66
$$&X(XX&&		] 	]s   /B--B6c                 F   t        t        |            }d}t        j                         j	                  d      }|| dz  }|j                  dd       d|j                  t        |      <   |j                  |d	d
d       || dz  }|j                         sJ d       y)u8   5만 줄 초과 시 _part2.jsonl 파일이 생성된다.r   i  r   r   rr   r   r   iQ  r   u    로테이션 트리거 메시지Fr   r  u:   50001줄 초과 시 _part2.jsonl이 생성되어야 한다N)	r	   r   r   r   r   r1  _jsonl_line_countr   r   r  s          r   $test_rotation_over_50k_creates_part2z6TestJSONLRotation.test_rotation_over_50k_creates_part2*  s     c(m<''
35' 00
b73 27c*o.!24V_de5' 66
  "`$``"r    c           	         t        j                         j                  d      }d}|| dz  }t        j                  ddt        j                         j                         d|ddd	      }|j                  |d
z   d       || dz  }t        j                  ddt        j                         j                         d|ddd	      }|j                  |d
z   d       t        t        |            }|j                  |       |j                  |d      }	|	D 
cg c]  }
|
j                   }}
d|v sJ d       d|v sJ d       yc c}
w )uB   load_today가 같은 날짜의 모든 part 파일을 로드한다.r   i  r   r   u   파트1 메시지Fr  )r   r   r   r   r   r  r)  rx  r   r   r  r   u   파트2 메시지Tr   r9   r.   u3   part1 파일의 메시지가 로드되어야 한다u3   part2 파일의 메시지가 로드되어야 한다N)r   r   r   r   r2  r0  r1  r	   r   r   r   r   )r   r   r   r   
part1_pathrecord1r  record2r   r   mtextss               r   test_load_today_loads_all_partsz1TestJSONLRotation.test_load_today_loads_all_parts;  sW   ''
3 5' 00
**++%\\^557"& 

 	gnw? 5' 66
**%+%\\^557"& 

 	gnw? c(m<w??7#?6!)*A**"e+b-bb+"e+b-bb+ +s   Ec                    t        t        |            }t        j                         j	                  d      }|| dz  }|j                  dd       |j                  |      }t        |      |j                  v sJ d       |j                  t        |         }t        dt        d	      
      5  |j                  |      }ddd       ||k(  sJ |k(  sJ |j                  t        |         |dz   k(  sJ y# 1 sw Y   8xY w)uA   줄 수가 캐시되어 매번 파일을 전체 읽지 않는다.r   r   r   zline1
line2
line3
r   r   u,   첫 호출 후 캐시가 채워져야 한다rp  u   파일 접근 금지rr  Nr   )
r	   r   r   r   r   r1  _get_jsonl_path_with_rotationr  r   IOError)r   r   r   r   r   path1cached_countpath2s           r   test_line_count_cachedz(TestJSONLRotation.test_line_count_cachedf  s
    c(m<''
35' 00
5H 11*=:#"7"77g9gg7,,S_= ?8N0OP 	B55jAE	B 
"""
"""$$S_59IIII	B 	Bs   1C::DN)r!   r"   r#   r$   r  r  r  r  r%   r    r   r  r    s    LY a")cVJr    r  c                       e Zd ZdZd Zd Zy)TestKoreanPromptHintsu2   한국어 특성 대응 프롬프트 힌트 확인c                     t        t        |            dj                  ddd       fd}t        j                   |              y)	uV   _do_generate_summary 프롬프트에 '주어는 자주 생략' 힌트가 포함된다.r   i  r   u   주어 생략 테스트Fr   c            
        K   t        dt              5 } t        j                  dg dg g ddd      | _        j                         d {    | j                  d	   d	   }d
|v rd|v sJ 	 d d d        y 7 *# 1 sw Y   y xY ww)Nr   r   ri  r  r  r  Fr)  r   u   주어u   생략r   r   r   r2  r   _do_generate_summaryr   r   s     r   r   zDTestKoreanPromptHints.test_korean_subject_omission_hint.<locals>.run  s     8yQ GU`+/::#+&(%.)+(*+8 "'
,( ..w777'11!4Q7	9,Y1FFF1FG G 8G Gs.   B8A:A8 A:/	B8A::B?BNr  r  s      @@r   !test_korean_subject_omission_hintz7TestKoreanPromptHints.test_korean_subject_omission_hint  sC     c(m<!24MV[\	G" 	CEr    c                     t        t        |            dj                  ddd       fd}t        j                   |              y)	uO   _do_generate_summary 프롬프트에 '감탄사' 무시 힌트가 포함된다.r   i  r   u   감탄사 테스트 ㅋㅋFr   c            
        K   t        dt              5 } t        j                  dg dg g ddd      | _        j                         d {    | j                  d	   d	   }d
|v sJ 	 d d d        y 7 &# 1 sw Y   y xY ww)Nr   r   ri  r  r  r  Fr)  r   u	   감탄사r  r   s     r   r   zGTestKoreanPromptHints.test_korean_interjection_ignore_hint.<locals>.run  s     8yQ 0U`+/::#+&(%.)+(*+8 "'
,( ..w777'11!4Q7	"i///0 0 80 0s.   B8A6A4A6+	B4A66A?;BNr  r  s      @@r   $test_korean_interjection_ignore_hintz:TestKoreanPromptHints.test_korean_interjection_ignore_hint  sB     c(m<!24PY^_	0" 	CEr    N)r!   r"   r#   r$   r  r  r%   r    r   r  r  }  s    <2r    r  c                       e Zd ZdZd Zy)TestGetContextDefaultLimitu+   get_context() 기본 limit 변경 테스트c                     t        d      }d}t        d      D ]  }|j                  |d| d| d        |j                  |      }t	        |      d	k(  sJ y)
u"   기본 limit이 30으로 변경됨Nr   i  #   userzmsg Fr      r   )r   r   r   r4   rY   s        r   test_default_limit_is_30z3TestGetContextDefaultLimit.test_default_limit_is_30  sf     d3r 	KAOOGtA3Z4sEOJ	K)6{b   r    N)r!   r"   r#   r$   r  r%   r    r   r  r    s
    5!r    r  c                   "    e Zd ZdZd Zd Zd Zy)TestRollingSummaryu   롤링 서머리 테스트c                     ddl mm t        d      dt	        d      D ]  }j                  d| d| d	
        fd}t        j                   |              y)uB   generate_rolling_summary() 호출 시 _rolling_summaries에 저장r   )r   r   Nr   i  rH   bot   의견 Tr   c                     K    d      5 } d| _         j                         d {   }d d d        dk(  sJ j                     dk(  sJ y 7 (# 1 sw Y   'xY ww)Nr   r   u   요약: 핵심 논점 정리...)r   generate_rolling_summary_rolling_summaries)r   rY   r   r   r   r   s     r   r   z;TestRollingSummary.test_rolling_summary_stored.<locals>.run  sz     8yQ EU`+L(";;GDDE >>>>))'26WWWW EE Es+   A!AAA$A!AAA!)unittest.mockr   r   r	   r3   r   r   r   )r   r4   r   r   r   r   r   s      @@@@r   test_rolling_summary_storedz.TestRollingSummary.test_rolling_summary_stored  s`    2 d3q 	LAOOGs1#Y'!dOK	L	X 	CEr    c                 \    t        d      fd}t        j                   |              y)u(   메시지 없으면 빈 문자열 반환Nr   c                  R   K   j                  d       d {   } | dk(  sJ y 7 w)Ni  rr   r  )rY   r   s    r   r   zCTestRollingSummary.test_rolling_summary_empty_messages.<locals>.run  s+     77<<FR<< =   '%')r	   r   r   )r   r   r   s     @r   #test_rolling_summary_empty_messagesz6TestRollingSummary.test_rolling_summary_empty_messages  s"     d3	  	CEr    c                 <   t        d      dt        d      D ]  }j                  d| d| d        t        j                         j                  d	      }d
j                  |<   dj                  <   fd}t        j                   |              y)u-   LLM 예산 초과 시 기존 서머리 반환Nr   i  rH   r  r  Tr   r   r   r  c                  R   K   j                         d {   } | dk(  sJ y 7 w)Nr  r  rh  s    r   r   zDTestRollingSummary.test_rolling_summary_budget_exceeded.<locals>.run  s.     77@@F_,,, Ar   )
r	   r3   r   r   r   r   rU  r  r   r   )r   r4   r   r   r   r   s       @@r   $test_rolling_summary_budget_exceededz7TestRollingSummary.test_rolling_summary_budget_exceeded  s     d3q 	LAOOGs1#Y'!dOK	L ''
3%'E"*9w'	- 	CEr    N)r!   r"   r#   r$   r  r  r  r%   r    r   r  r    s    $$r    r  c                   .    e Zd ZdZd Zd Zd Zd Zd Zy)TestFormatContextWithPhaseu-   format_context() Phase 프롬프트 테스트c                 ~    t        d      }d}|j                  |ddd       |j                  |dd	
      }d|v sJ y)u*   phase='diverge'일 때 발산 프롬프트Nr   i  r      의견입니다Tr   rT   divergephaseu=   새로운 관점과 아이디어를 자유롭게 제시하라rV   rX   s       r   test_diverge_phase_promptz4TestFormatContextWithPhase.test_diverge_phase_prompt  sM     d3.?M##G->i#PNRXXXXr    c                 ~    t        d      }d}|j                  |ddd       |j                  |dd	
      }d|v sJ y)u+   phase='converge'일 때 수렴 프롬프트Nr   i  r   r  Tr   rT   converger
  u    공통점을 먼저 정리하라rV   rX   s       r   test_converge_phase_promptz5TestFormatContextWithPhase.test_converge_phase_prompt	  sL     d3.?M##G->j#Q1V;;;r    c                 ~    t        d      }d}|j                  |ddd       |j                  |dd	
      }d|v sJ y)u,   phase='consensus'일 때 합의 프롬프트Nr   i  r   r  Tr   rT   r  r
  u    최종 합의문을 작성하라rV   rX   s       r   test_consensus_phase_promptz6TestFormatContextWithPhase.test_consensus_phase_prompt	  sL     d3.?M##G->k#R1V;;;r    c                 z    t        d      }d}|j                  |ddd       |j                  |d      }d	|v sJ y)
u,   phase=None일 때 기존 기본 프롬프트Nr   i  r   r  Tr   rT   u4   중복되지 않는 새로운 관점을 제시해라rV   rX   s       r   test_no_phase_default_promptz7TestFormatContextWithPhase.test_no_phase_default_prompt	  sG     d3.?M##G->?EOOOr    c                     t        d      }d}|j                  |ddd       d|j                  |<   |j                  |d	      }d
|v sJ d|v sJ y)u3   롤링 서머리가 format_context 결과에 포함Nr   i  r   r  Tr   u#   이것은 롤링 서머리입니다rT   u   [현재 토론 요약])r	   r   r  rW   rX   s       r   test_rolling_summary_includedz8TestFormatContextWithPhase.test_rolling_summary_included	  se     d3.?M*Ow'##G->?'61114>>>r    N)	r!   r"   r#   r$   r  r  r  r  r  r%   r    r   r  r    s     7Y<<P?r    r  );r   r   r2  sysr   pathlibr   r  r   r   r   rq  insertjoindirname__file__pytestconversation_memoryr   r	   r   r'   r7   rD   rR   r\   rp   r{   r   r   r   r   r   r!  rK  rn  r}  r  r  r  r  r  r  r  r  r"  r,  r<  rS  rk  rx  r  r  r  r  r  r  r  r  r%   r    r   <module>r     s     	 
   0 0 277<< 94@ A  ?* *(3 3"0 00" "4C C&%q %qP/ /<F F>' '$& &NM* M*`K3 K3\z zzn# n#bJ JZ06 06fP. P.f? ?D -Q -Qj, ,^?3 ?3D#4 #4LR< R<jl l^+ +\" ":K K\I IX# #>B BJ&? &?\O Ody yxcJ cJL3 3v! !/ /d+? +?r    