
    Si+                       d Z ddlmZ ddlZddlmZ ddlZej                  j                  d e	 ee
      j                  j                  j                               ddlmZmZmZmZmZ ddlmZ ej(                  d        Zej(                  d        Z G d	 d
      Z G d d      Z G d d      Z G d d      Z G d d      Zy)uC   utils/session_search.py 테스트 스위트 (TDD — RED → GREEN)    )annotationsN)Path)_format_conversation_resolve_lineage_root_truncate_around_matchessanitize_fts5_querysearch_sessions)SessionStorec              #  h   K   t        | dz        }t        |      }| |j                          yw)u.   임시 DB 경로를 사용하는 SessionStore.ztest_search.db)db_pathN)strr
   close)tmp_pathr   ss      P/home/jay/workspace/.worktrees/task-2117-dev1/utils/tests/test_session_search.pystorer      s0      (--.GW%A
GGGIs   02c                   | j                  dd       | j                  ddd       | j                  ddd       | j                  d	d       | j                  d	dd
       | j                  dd       | j                  ddd       | S )u5   검색 테스트용 세션/메시지가 있는 store.
sess-alphaapisourceuserzhello world FTS5 testrolecontent	assistantzresponse to helloz	sess-betazpython programming rocksz
sess-gammazcompletely unrelated content)create_sessionappend_message)r   s    r   populated_storer   $   s     
e4	F<ST	KATU	U3	6;UV	e4	F<Z[L    c                  X    e Zd ZdZd Zd Zd Zd Zd Zd Z	d Z
d	 Zd
 Zd Zd Zd Zy)TestSanitizeFts5Queryu   FTS5 쿼리 특수문자 처리c                $    t        d      dk(  sJ y)u,   일반 단어는 변경 없이 반환된다.helloNr   selfs    r   test_plain_word_unchangedz/TestSanitizeFts5Query.test_plain_word_unchanged;   s    "7+w666r    c                2    t        d      }d|vsJ d|vsJ y)u   괄호가 제거된다.z(hello world)()Nr%   r'   results     r   test_removes_parenthesesz.TestSanitizeFts5Query.test_removes_parentheses?   s(    $_5&   &   r    c                &    t        d      }d|vsJ y)u   + 연산자가 제거된다.zhello+world+Nr%   r,   s     r   test_removes_plus_operatorz0TestSanitizeFts5Query.test_removes_plus_operatorE   s    $]3&   r    c                &    t        d      }d|vsJ y)u   * 연산자가 제거된다.zhello**Nr%   r,   s     r   test_removes_star_operatorz0TestSanitizeFts5Query.test_removes_star_operatorJ   s    $X.&   r    c                &    t        d      }d|v sJ y)u!   인용구("...")는 보호된다.z"hello world"Nr%   r,   s     r   test_quoted_phrase_preservedz2TestSanitizeFts5Query.test_quoted_phrase_preservedO   s    $_5&(((r    c                >    t        d      }d|vsJ d|v sJ d|v sJ y)u   AND 연산자가 제거된다.zhello AND worldANDr$   worldNr%   r,   s     r   test_removes_and_operatorz/TestSanitizeFts5Query.test_removes_and_operatorT   s7    $%67F"""&   &   r    c                &    t        d      }d|vsJ y)u   OR 연산자가 제거된다.zhello OR worldORNr%   r,   s     r   test_removes_or_operatorz.TestSanitizeFts5Query.test_removes_or_operator[   s    $%566!!!r    c                &    t        d      }d|vsJ y)u   NOT 연산자가 제거된다.zhello NOT worldNOTNr%   r,   s     r   test_removes_not_operatorz/TestSanitizeFts5Query.test_removes_not_operator`   s    $%67F"""r    c                &    t        d      }d|v sJ y)u1   ANDROID 같은 단어 안의 AND는 보호된다.ANDROIDNr%   r,   s     r   test_android_preservedz,TestSanitizeFts5Query.test_android_preservede   s    $Y/F"""r    c                &    t        d      }d|v sJ y)u3   하이픈 단어(chat-send)가 인용 처리된다.z	chat-sendz"chat-send"Nr%   r,   s     r   test_hyphenated_word_quotedz1TestSanitizeFts5Query.test_hyphenated_word_quotedj   s    $[1&&&r    c                $    t        d      dk(  sJ y)u#   빈 쿼리는 빈 문자열 반환. Nr%   r&   s    r   test_empty_query_returns_emptyz4TestSanitizeFts5Query.test_empty_query_returns_emptyo   s    "2&",,,r    c                B    t        d      }d|j                         vsJ y)u!   연속 공백이 정규화된다.zhello   worldz  N)r   stripr,   s     r   test_whitespace_normalizedz0TestSanitizeFts5Query.test_whitespace_normalizeds   s     $_56<<>)))r    N)__name__
__module____qualname____doc__r(   r.   r1   r4   r6   r:   r=   r@   rC   rE   rH   rK    r    r   r"   r"   8   sA    )7!!
!
)
!"
#
#
'
-*r    r"   c                  F    e Zd ZdZd Zd Zd Zd Zd Zd Z	d Z
d	 Zd
 Zy)TestSearchSessionsu   FTS5 세션 검색 동작c                L    t        d|      }t        |t              sJ d|v sJ y)u(   기본 검색이 결과를 반환한다.r$   resultsN)r	   
isinstancedictr'   r   r-   s      r   !test_basic_search_returns_resultsz4TestSearchSessions.test_basic_search_returns_results   s,     /:&$'''F"""r    c                \    t        d|      }|d   D cg c]  }|d   	 }}d|v sJ yc c}w )u(   매칭 세션이 결과에 포함된다.r$   rT   
session_idr   Nr	   r'   r   r-   rsession_idss        r   test_matching_session_foundz.TestSearchSessions.test_matching_session_found   s>     /:06y0AB1qBB{*** Cs   )c                F    t        d|d      }t        |d         dk  sJ y)u1   limit 파라미터가 결과 수를 제한한다.r$      )limitrT   Nr	   lenrW   s      r   test_limit_respectedz'TestSearchSessions.test_limit_respected   s(     /C6)$%***r    c                0    t        d|      }|d   g k(  sJ y)u)   빈 쿼리는 빈 결과를 반환한다.rG   rT   Nr[   rW   s      r   &test_empty_query_returns_empty_resultsz9TestSearchSessions.test_empty_query_returns_empty_results   s!     _5i B&&&r    c                0    t        d|      }|d   g k(  sJ y)u#   매칭 없는 쿼리는 빈 결과.xyzzy_no_match_everrT   Nr[   rW   s      r   test_no_match_returns_emptyz.TestSearchSessions.test_no_match_returns_empty   s"     !6Hi B&&&r    c                    t        d|dg      }|d   D cg c]  }|d   	 }}d|vst        |d         dk(  sJ yyc c}w )	u:   role_filter=['user']이면 user 메시지만 검색한다.responser   )role_filterrT   rZ   r   r   Nrc   r\   s        r   test_role_filter_user_onlyz-TestSearchSessions.test_role_filter_user_only   sY     _6(S06y0AB1qBB;.#fY6G2HA2MMM2M. Cs   >c                `    t        d|d      }|d   D cg c]  }|d   	 }}d|vsJ yc c}w )uN   current_session_id가 설정되면 해당 세션이 결과에서 제외된다.r$   r   )current_session_idrT   rZ   Nr[   r\   s        r   test_current_session_excludedz0TestSearchSessions.test_current_session_excluded   s@     /l[06y0AB1qBB;... Cs   +c                @    t        d|      }|d   rd|d   d   v sJ yy)u*   결과 항목에 summary 필드가 있다.r$   rT   summaryr   Nr[   rW   s      r   test_result_contains_summaryz/TestSearchSessions.test_result_contains_summary   s3     /:)y 1! 4444 r    c                @    t        d|      }|d   rd|d   d   v sJ yy)u-   결과 항목에 session_id 필드가 있다.r$   rT   rZ   r   Nr[   rW   s      r   test_result_contains_session_idz2TestSearchSessions.test_result_contains_session_id   s3     /:)6)#4Q#7777 r    N)rL   rM   rN   rO   rX   r_   re   rg   rj   rn   rq   rt   rv   rP   r    r   rR   rR   ~   s3    ##++
'
'
N/58r    rR   c                  .    e Zd ZdZd Zd Zd Zd Zd Zy)TestResolveLineageRootu   위임 체인 루트 탐색c                L    |j                  dd       t        d|      dk(  sJ y)u0   parent_session_id가 없으면 자신이 루트.z	root-onlyr   r   Nr   r   r'   r   s     r    test_single_session_returns_selfz7TestResolveLineageRoot.test_single_session_returns_self   s*    [7$[%8KGGGr    c                t    |j                  dd       |j                  ddd       t        d|      dk(  sJ y)u,   부모 체인을 타고 루트를 찾는다.zroot-1r   r   zchild-1r   parent_session_idNrz   r{   s     r   test_child_returns_rootz.TestResolveLineageRoot.test_child_returns_root   s?    Xe4YuQ$Y6(BBBr    c                    |j                  dd       |j                  ddd       |j                  ddd       t        d|      dk(  sJ y)u,   3단계 체인에서도 루트를 찾는다.r]   r   r   c1r~   c2Nrz   r{   s     r   test_deep_chain_returns_rootz3TestResolveLineageRoot.test_deep_chain_returns_root   sT    S/T%3GT%4H$T51S888r    c                &    t        d|      dk(  sJ y)uI   존재하지 않는 session_id는 자신을 반환한다 (안전 폴백).nonexistentN)r   r{   s     r   !test_unknown_session_returns_selfz8TestResolveLineageRoot.test_unknown_session_returns_self   s    $]E:mKKKr    c                .   ddl }|j                  dd       |j                  ddd       |j                  |j                        }|j	                  d       |j                          |j                          t        d|      }t        |t              sJ y)	u<   순환 참조가 있어도 무한루프 없이 종료된다.r   Nzcycle-ar   r   zcycle-br~   zJUPDATE sessions SET parent_session_id='cycle-b' WHERE session_id='cycle-a')
sqlite3r   connectr   executecommitr   r   rU   r   )r'   r   r   connr-   s        r   test_cycle_guardz'TestResolveLineageRoot.test_cycle_guard   s|     	Yu5Yu	Ru}}-ab

&y%8&#&&&r    N)	rL   rM   rN   rO   r|   r   r   r   r   rP   r    r   rx   rx      s!    %H
C9L'r    rx   c                  (    e Zd ZdZd Zd Zd Zd Zy)TestTruncateAroundMatchesu0   쿼리 매칭 위치 중심 텍스트 창 추출c                0    d}t        |dd      }d|v sJ y)u;   max_chars보다 짧은 텍스트는 그대로 반환된다.zhello worldr$   i  	max_charsNr   r'   textr-   s      r   test_short_text_returned_as_isz8TestTruncateAroundMatches.test_short_text_returned_as_is   s#    )$4H&   r    c                J    ddz  }t        |dd      }t        |      dk  sJ y)u+   max_chars보다 긴 텍스트는 잘린다.xi@ xxxd   r      Nr   rd   r   s      r   test_long_text_truncatedz2TestTruncateAroundMatches.test_long_text_truncated   s+    W})$E6{c!!!r    c                L    ddz  }|dz   ddz  z   }t        |dd      }d|v sJ y)u/   매칭 위치 주변 텍스트가 포함된다.aiP  TARGETbi  r   Nr   )r'   prefixr   r-   s       r   test_match_position_includedz6TestTruncateAroundMatches.test_match_position_included   s:    v 3</)$CH6!!!r    c                J    ddz  }t        |dd      }t        |      dk  sJ y)u2   빈 쿼리는 텍스트 앞부분을 반환한다.zhello world i'  rG   r   r   r   Nr   r   s      r   test_empty_query_returns_headz7TestTruncateAroundMatches.test_empty_query_returns_head   s,    %)$cB6{c!!!r    N)rL   rM   rN   rO   r   r   r   r   rP   r    r   r   r      s    :!"""r    r   c                  "    e Zd ZdZd Zd Zd Zy)TestFormatConversationu   메시지 직렬화c                >    t        g       }t        |t              sJ y)u2   빈 메시지 목록은 문자열을 반환한다.N)r   rU   r   r,   s     r   "test_empty_messages_returns_stringz9TestFormatConversation.test_empty_messages_returns_string  s    %b)&#&&&r    c                4    ddddg}t        |      }d|v sJ y)u)   메시지 role이 포맷에 포함된다.r   r$   z
2024-01-01)r   r   
created_atNr   r'   msgsr-   s      r   test_message_role_includedz1TestFormatConversation.test_message_role_included  s)    G<PQ%d+r    c                2    dddg}t        |      }d|v sJ y)u,   메시지 content가 포맷에 포함된다.r   unique_content_xyzr   Nr   r   s      r   test_message_content_includedz4TestFormatConversation.test_message_content_included  s(    ,@AB%d+#v---r    N)rL   rM   rN   rO   r   r   r   rP   r    r   r   r     s    '
 .r    r   )rO   
__future__r   syspathlibr   pytestpathinsertr   __file__parentutils.session_searchr   r   r   r   r	   utils.session_storer
   fixturer   r   r"   rR   rx   r   r   rP   r    r   <module>r      s    I " 
   3tH~,,33::; <  -    &>* >*L68 68|&' &'\" "D. .r    