
    Ui(                       d Z ddlmZ ddlZddlmc mZ ddl	Z	ddl
Z
ddlZddlZddlmZ ddlZe
j                   j#                  d e ee      j(                  j(                  j(                               ddlmZmZ ej0                  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 y)uB   utils/session_store.py 테스트 스위트 (TDD — RED → GREEN)    )annotationsN)Path)SessionStoresanitize_titlec              #  h   K   t        | dz        }t        |      }| |j                          yw)u;   임시 DB 경로를 사용하는 SessionStore 인스턴스.ztest_sessions.db)db_pathN)strr   close)tmp_pathr   ss      5/home/jay/workspace/utils/tests/test_session_store.pystorer      s0      (//0GW%A
GGGIs   02c                  @    e Zd ZdZd Zd Zd Zd Zd Zd Z	d Z
d	 Zy
)TestSessionCRUDu-   세션 생성 / 조회 / 종료 기본 동작c                *    |j                  dd       y)u/   create_session()이 예외 없이 완료된다.zsess-001apisourceN)create_sessionselfr   s     r   test_create_sessionz#TestSessionCRUD.test_create_session(   s    Z6    c                   |j                  ddd       |j                  d      }d}||u}|st        j                  d|fd||f      dt	        j
                         v st        j                  |      rt        j                  |      ndt        j                  |      d	z  }d
d|iz  }t        t        j                  |            dx}}|d   }d}||k(  }|slt        j                  d|fd||f      t        j                  |      t        j                  |      dz  }	dd|	iz  }
t        t        j                  |
            dx}x}}|d   }d}||k(  }|slt        j                  d|fd||f      t        j                  |      t        j                  |      dz  }	dd|	iz  }
t        t        j                  |
            dx}x}}|d   }d}||k(  }|slt        j                  d|fd||f      t        j                  |      t        j                  |      dz  }	dd|	iz  }
t        t        j                  |
            dx}x}}y)u>   create_session() 후 get_session()으로 조회할 수 있다.zsess-002clizgpt-4)r   modelNis not)z%(py0)s is not %(py3)srowpy0py3assert %(py5)spy5
session_id==z%(py1)s == %(py4)spy1py4assert %(py6)spy6r   r   )
r   get_session
@pytest_ar_call_reprcompare@py_builtinslocals_should_repr_global_name	_safereprAssertionError_format_explanation)r   r   r   @py_assert2@py_assert1@py_format4@py_format6@py_assert0@py_assert3@py_format5@py_format7s              r    test_get_session_returns_createdz0TestSessionCRUD.test_get_session_returns_created,   sm   ZWE
+s$s$ss$< .J. J.... J... ...J.......8}%%}%%%%}%%%}%%%%%%%%%%7|&w&|w&&&&|w&&&|&&&w&&&&&&&r   c                   |j                   }d} ||      }d}||u }|st        j                  d|fd||f      dt        j                         v st        j
                  |      rt        j                  |      ndt        j                  |      t        j                  |      t        j                  |      t        j                  |      dz  }dd|iz  }t        t        j                  |            dx}x}x}x}}y)	u6   존재하지 않는 session_id 조회 시 None 반환.zdoes-not-existNiszQ%(py6)s
{%(py6)s = %(py2)s
{%(py2)s = %(py0)s.get_session
}(%(py4)s)
} is %(py9)sr   r!   py2r+   r-   py9assert %(py11)spy11)	r.   r/   r0   r1   r2   r3   r4   r5   r6   	r   r   r8   r<   @py_assert5@py_assert8@py_assert7@py_format10@py_format12s	            r   %test_get_session_missing_returns_nonez5TestSessionCRUD.test_get_session_missing_returns_none5   s      :!1: !12:d:2d::::2d::::::u:::u::: :::!1:::2:::d::::::::r   c                p   |j                  dd       |j                  dd       |j                  d      }|d   }d}||k(  }|slt        j                  d|fd||f      t        j
                  |      t        j
                  |      d	z  }d
d|iz  }t        t        j                  |            dx}x}}|d   }d}||u}|slt        j                  d|fd||f      t        j
                  |      t        j
                  |      d	z  }d
d|iz  }t        t        j                  |            dx}x}}y)u<   end_session()으로 세션이 종료 상태로 변경된다.zsess-003r   r   	user_exit
end_reasonrS   r&   r(   r)   r,   r-   Nended_atr   z%(py1)s is not %(py4)s)r   end_sessionr.   r/   r0   r4   r5   r6   r   r   r   r;   r<   r7   r=   r>   s           r   test_end_sessionz TestSessionCRUD.test_end_session9   s    Z6*=
+< /K/ K//// K/// ///K///////:*d*d****d******d*******r   c                2   |j                  dd       |j                  dd       |j                         }|D cg c]  }|d   	 }}d}||v }|st        j                  d|fd||f      t        j                  |      d	t        j                         v st        j                  |      rt        j                  |      nd	d
z  }dd|iz  }t        t        j                  |            dx}}d}||v }|st        j                  d|fd||f      t        j                  |      d	t        j                         v st        j                  |      rt        j                  |      nd	d
z  }dd|iz  }t        t        j                  |            dx}}yc c}w )u4   list_sessions()가 생성된 세션을 반환한다.zsess-list-1r   r   zsess-list-2r   r%   inz%(py1)s in %(py3)sidsr*   r"   r#   r$   N
r   list_sessionsr/   r0   r4   r1   r2   r3   r5   r6   	r   r   rowsrr]   r;   r7   r9   r:   s	            r   "test_list_sessions_returns_createdz2TestSessionCRUD.test_list_sessions_returns_createdA   s   ]59]59""$(,-1q--#}####}###}#################}####}###}################ .s   Fc                6   |j                  dd       |j                  dd       |j                  d      }|D cg c]  }|d   	 }}d}||v }|st        j                  d|fd||f      t        j                  |      d	t        j                         v st        j                  |      rt        j                  |      nd	d
z  }dd|iz  }t        t        j                  |            dx}}d}||v}|st        j                  d|fd||f      t        j                  |      d	t        j                         v st        j                  |      rt        j                  |      nd	d
z  }dd|iz  }t        t        j                  |            dx}}yc c}w )u+   source 필터가 올바르게 동작한다.z	src-api-1r   r   z	src-cli-1r   r%   rZ   r\   r]   r^   r#   r$   Nnot inz%(py1)s not in %(py3)sr_   ra   s	            r   #test_list_sessions_filter_by_sourcez3TestSessionCRUD.test_list_sessions_filter_by_sourceJ   s	   [7[7""%"0(,-1q--!{c!!!!{c!!!{!!!!!!c!!!c!!!!!!!%{#%%%%{#%%%{%%%%%%#%%%#%%%%%%% .s   Fc                   t        d      D ]  }|j                  d| d        |j                  d      }t        |      }d}||k  }|st	        j
                  d|fd||f      d	t        j                         v st	        j                  t              rt	        j                  t              nd	d
t        j                         v st	        j                  |      rt	        j                  |      nd
t	        j                  |      t	        j                  |      dz  }dd|iz  }t        t	        j                  |            dx}x}}y)u4   limit 파라미터가 반환 개수를 제한한다.   zlim-testr      limit)<=)z0%(py3)s
{%(py3)s = %(py0)s(%(py1)s)
} <= %(py6)slenrb   r!   r*   r"   r-   assert %(py8)spy8N)ranger   r`   rq   r/   r0   r1   r2   r3   r4   r5   r6   )	r   r   irb   r7   rJ   @py_assert4r>   @py_format9s	            r   test_list_sessions_limitz(TestSessionCRUD.test_list_sessions_limitS   s    q 	<A  4sF ;	<"""+4yAyA~yAss44yAr   c                t   |j                  dd       |j                  ddd       |j                  d      }|d   }d}||k(  }|slt        j                  d|fd||f      t        j                  |      t        j                  |      d	z  }d
d|iz  }t        t        j                  |            dx}x}}y)u5   parent_session_id가 DB에 올바르게 저장된다.zparent-1r   r   zchild-1)r   parent_session_idr{   r&   r(   r)   r,   r-   N)r   r.   r/   r0   r4   r5   r6   rW   s           r   test_parent_session_id_storedz-TestSessionCRUD.test_parent_session_id_storedZ   s    Z6Yu
S	*&'5:5':5555':555'555:5555555r   N)__name__
__module____qualname____doc__r   r?   rO   rX   rd   ri   ry   r|    r   r   r   r   %   s-    77';+$&6r   r   c                  (    e Zd ZdZd Zd Zd Zd Zy)TestMessagesu   메시지 append / get 동작c                R    |j                  dd       |j                  ddd       y)u/   append_message()가 예외 없이 완료된다.z
msg-sess-1r   r   userhellorolecontentN)r   append_messager   s     r   test_append_messagez TestMessages.test_append_messagej   s*    \%8\Hr   c                   |j                  dd       |j                  ddd       |j                  ddd       |j                  d      }t        |      }d	}||k(  }|st	        j
                  d
|fd||f      dt        j                         v st	        j                  t              rt	        j                  t              nddt        j                         v st	        j                  |      rt	        j                  |      ndt	        j                  |      t	        j                  |      dz  }dd|iz  }t        t	        j                  |            dx}x}}|d   d   }d}	||	k(  }|slt	        j
                  d
|fd||	f      t	        j                  |      t	        j                  |	      dz  }
dd|
iz  }t        t	        j                  |            dx}x}}	|d   d   }d}	||	k(  }|slt	        j
                  d
|fd||	f      t	        j                  |      t	        j                  |	      dz  }
dd|
iz  }t        t	        j                  |            dx}x}}	y)u6   get_messages()가 추가된 메시지를 반환한다.z
msg-sess-2r   r   r   hir   	assistantr      r&   z0%(py3)s
{%(py3)s = %(py0)s(%(py1)s)
} == %(py6)srq   msgsrr   rs   rt   Nr   r   r(   r)   r,   r-      )r   r   get_messagesrq   r/   r0   r1   r2   r3   r4   r5   r6   )r   r   r   r7   rJ   rw   r>   rx   r;   r<   r=   s              r   "test_get_messages_returns_appendedz/TestMessages.test_get_messages_returns_appendedo   s~   \%8\E\WM!!,/4yAyA~yAss44yAAwv(&(&((((&((((((&(((((((Awv-+-+----+------+-------r   c                   |j                  dd       |j                  ddddddid	g
       |j                  d      }|d   d   }d}||u}|slt        j                  d|fd||f      t        j
                  |      t        j
                  |      dz  }dd|iz  }t        t        j                  |            dx}x}}y)u4   tool_calls JSON이 올바르게 저장/조회된다.z
msg-sess-3r   r   r   call_1functionnamefoo)idtyper   )r   
tool_callsr   r   Nr   rU   r)   r,   r-   )r   r   r   r/   r0   r4   r5   r6   )r   r   r   r;   r<   r7   r=   r>   s           r   test_message_with_tool_callsz)TestMessages.test_message_with_tool_callsy   s    \%8'&RWYZ 	 	

 !!,/Aw|$0D0$D0000$D000$000D0000000r   c                $   |j                  dd       t        d      D ]  }|j                  ddt        |             ! |j	                  d      }|D cg c]  }|d   	 }}g d}||k(  }|st        j                  d	|fd
||f      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      dz  }dd|iz  }	t        t        j                  |	            dx}}yc c}w )u7   메시지가 추가 순서(created_at)로 반환된다.z
msg-sess-4r   r   rm   r   r   r   )012r&   z%(py0)s == %(py3)scontentsr    r#   r$   N)r   ru   r   r	   r   r/   r0   r1   r2   r3   r4   r5   r6   )
r   r   rv   r   mr   r7   r8   r9   r:   s
             r   #test_message_ordering_by_created_atz0TestMessages.test_message_ordering_by_created_at   s    \%8q 	LA  FCF K	L!!,/*./QAiL//**x?****x?******x***x***?******* 0s   DN)r}   r~   r   r   r   r   r   r   r   r   r   r   r   g   s    'I
.	1+r   r   c                      e Zd ZdZd Zy)TestWALModeu   SQLite WAL 저널 모드 확인c                   t        j                  |j                        }|j                  d      j	                         d   }|j                          d}||k(  }|st        j                  d|fd||f      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      dz  }dd	|iz  }t        t        j                  |            d
x}}y
)u-   DB의 journal_mode가 WAL임을 확인한다.zPRAGMA journal_mode;r   walr&   r   moder    r#   r$   N)sqlite3connectr   executefetchoner
   r/   r0   r1   r2   r3   r4   r5   r6   )r   r   connr   r7   r8   r9   r:   s           r   test_wal_mode_enabledz!TestWALMode.test_wal_mode_enabled   s    u}}-||23<<>qA

tu}tuttu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y)TestFTS5u+   messages_fts 가상 테이블 기본 동작c                   t        j                  |j                        }|j                  d      j	                         D cg c]  }|d   	 }}|j                          d}||v }|st        j                  d|fd||f      t        j                  |      dt        j                         v st        j                  |      rt        j                  |      nddz  }dd	|iz  }t        t        j                  |            d
x}}y
c c}w )u5   messages_fts 가상 테이블이 생성되어 있다.z1SELECT name FROM sqlite_master WHERE type='table'r   messages_ftsrZ   r\   tablesr^   r#   r$   N)r   r   r   r   fetchallr
   r/   r0   r4   r1   r2   r3   r5   r6   )	r   r   r   rc   r   r;   r7   r9   r:   s	            r   test_fts5_table_existszTestFTS5.test_fts5_table_exists   s    u}}- $-` a j j lm1!A$mm

'~''''~'''~'''''''''''''''' ns   Dc                   |j                  dd       |j                  ddd       t        j                  |j                        }|j                  d      j                         }|j                          t        |      }d}||k\  }|st        j                  d	|fd
||f      dt        j                         v st        j                  t              rt        j                  t              nddt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      dz  }dd|iz  }t        t        j                   |            dx}x}}y)u0   FTS5 검색이 삽입된 내용을 반환한다.z
fts-sess-1r   r   r   unique_keyword_xyzr   zNSELECT content FROM messages_fts WHERE messages_fts MATCH 'unique_keyword_xyz'r   )>=)z0%(py3)s
{%(py3)s = %(py0)s(%(py1)s)
} >= %(py6)srq   rb   rr   rs   rt   N)r   r   r   r   r   r   r   r
   rq   r/   r0   r1   r2   r3   r4   r5   r6   )	r   r   r   rb   r7   rJ   rw   r>   rx   s	            r   test_fts5_search_basiczTestFTS5.test_fts5_search_basic   s    \%8\@TUu}}-||lmvvx

4yAyA~yAss44yAr   N)r}   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y)	TestPruneu   오래된 세션 정리c                   |j                  dd       |j                  dd       t        j                  |j                        }|j                  d       |j                          |j                          |j                  d       |j                  }d} ||      }d	}||u }|st        j                  d
|fd||f      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      t        j                  |      t        j                  |      dz  }dd|iz  }	t!        t        j"                  |	            d	x}x}x}x}}y	)uN   older_than_days=0으로 prune하면 ended_at이 있는 세션이 삭제된다.z	prune-oldr   r   timeoutrR   zOUPDATE sessions SET ended_at='2000-01-01T00:00:00' WHERE session_id='prune-old'r   older_than_daysNrA   rC   r   rD   rG   rH   )r   rV   r   r   r   r   commitr
   prune_sessionsr.   r/   r0   r1   r2   r3   r4   r5   r6   )
r   r   r   r8   r<   rJ   rK   rL   rM   rN   s
             r   test_prune_removes_old_sessionsz)TestPrune.test_prune_removes_old_sessions   s   [7+)< u}}-fg

Q/  55 -55-5555-555555u555u555 555555-55555555555r   c                H   |j                  dd       |j                  d       |j                  }d} ||      }d}||u}|st        j                  d|fd||f      d	t        j                         v st        j                  |      rt        j                  |      nd	t        j                  |      t        j                  |      t        j                  |      t        j                  |      d
z  }dd|iz  }t        t        j                  |            dx}x}x}x}}y)u<   ended_at이 없는 (활성) 세션은 prune되지 않는다.zprune-activer   r   r   r   Nr   )zU%(py6)s
{%(py6)s = %(py2)s
{%(py2)s = %(py0)s.get_session
}(%(py4)s)
} is not %(py9)sr   rD   rG   rH   )r   r   r.   r/   r0   r1   r2   r3   r4   r5   r6   rI   s	            r    test_prune_keeps_active_sessionsz*TestPrune.test_prune_keeps_active_sessions   s    ^E:Q/  << 0<<0<<<<0<<<<<<u<<<u<<< <<<<<<0<<<<<<<<<<<r   N)r}   r~   r   r   r   r   r   r   r   r   r      s    !6=r   r   c                      e Zd ZdZd Zy)TestConcurrencyu&   threading.Lock 동시 쓰기 안전성c                   g fd}t        d      D cg c]  }t        j                  ||f       }}|D ]  }|j                           |D ]  }|j	                           g }|k(  }|st        j                  d|fd|f      dt        j                         v st        j                        rt        j                        ndt        j                  |      dz  }t        j                  d       d	z   d
|iz  }	t        t        j                  |	            dx}}j                  d      }
|
D cg c]  }|d   j                  d      s|d    }}t!        |      }d}||k(  }|st        j                  d|fd||f      dt        j                         v st        j                  t               rt        j                  t               nddt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      dz  }dd|iz  }t        t        j                  |            dx}x}}yc c}w c c}w )uI   여러 스레드에서 동시에 세션을 생성해도 예외가 없다.c                    	 j                  d|  d       j                  d|  dd|         y # t        $ r}j                  |       Y d }~y d }~ww xY w)Nconc-threadr   r   zmsg-r   )r   r   	Exceptionappend)idxeerrorsr   s     r   workerzCTestConcurrency.test_concurrent_writes_no_exception.<locals>.worker   sb    !$$uSE]8$D$$uSE]4PSu$V !a  !s   04 	AAA
   )targetargsr&   r   r   r    zErrors in threads: z
>assert %(py5)sr$   N   rn   r%   r   r   rq   conc_idsrr   rs   rt   )ru   	threadingThreadstartjoinr/   r0   r1   r2   r3   r4   _format_assertmsgr5   r6   r`   
startswithrq   )r   r   r   rv   threadstr7   r8   r9   r:   rb   rc   r   rJ   rw   r>   rx   r   s    `               @r   #test_concurrent_writes_no_exceptionz3TestConcurrency.test_concurrent_writes_no_exception   s   	! HMRyQ!9##6=QQ 	AGGI	 	AFFH	 ;v|;;;v;;;;;;v;;;v;;;;;;26(;;;;;;;""",-1YQ|_5O5OPW5XAlOYY8}""}""""}""""""s"""s""""""8"""8"""}"""""""""" R Zs   I0<I5I5N)r}   r~   r   r   r   r   r   r   r   r      s
    0#r   r   c                  "    e Zd ZdZd Zd Zd Zy)TestSanitizeTitleu'   제어문자 / zero-width 문자 제거c                   t        d      }d}||v}|st        j                  d|fd||f      t        j                  |      dt	        j
                         v st        j                  |      rt        j                  |      nddz  }dd|iz  }t        t        j                  |            d	x}}d
}||v}|st        j                  d|fd||f      t        j                  |      dt	        j
                         v st        j                  |      rt        j                  |      nddz  }dd|iz  }t        t        j                  |            d	x}}d}||v }|st        j                  d|fd||f      t        j                  |      dt	        j
                         v st        j                  |      rt        j                  |      nddz  }dd|iz  }t        t        j                  |            d	x}}y	)u+   ASCII 제어문자( -, )가 제거된다.zhello world! rf   rh   resultr^   r#   r$   Nzhelloworld!rZ   r\   	r   r/   r0   r4   r1   r2   r3   r5   r6   r   r   r;   r7   r9   r:   s         r   #test_sanitize_removes_control_charsz5TestSanitizeTitle.test_sanitize_removes_control_chars   s    56#vV####vV###v######V###V########vV####vV###v######V###V#######&}&&&&}&&&}&&&&&&&&&&&&&&&&r   c                   t        d      }d}||v}|st        j                  d|fd||f      t        j                  |      dt	        j
                         v st        j                  |      rt        j                  |      nddz  }dd|iz  }t        t        j                  |            d	x}}d
}||v }|st        j                  d|fd||f      t        j                  |      dt	        j
                         v st        j                  |      rt        j                  |      nddz  }dd|iz  }t        t        j                  |            d	x}}y	)u.   Zero-width 문자(U+200B 등)가 제거된다.u   he​llou   ​rf   rh   r   r^   r#   r$   Nr   rZ   r\   r   r   s         r   &test_sanitize_removes_zero_width_charsz8TestSanitizeTitle.test_sanitize_removes_zero_width_chars  s    .%xv%%%%xv%%%x%%%%%%v%%%v%%%%%%% w&    w&   w      &   &       r   c                |   d}t        |      }||k(  }|s#t        j                  d|fd||f      dt        j                         v st        j
                  t               rt        j                  t               nddt        j                         v st        j
                  |      rt        j                  |      ndt        j                  |      dt        j                         v st        j
                  |      rt        j                  |      nddz  }dd|iz  }t        t        j                  |            d	x}}y	)
u+   일반 텍스트는 변경되지 않는다.u   Hello, World! 안녕하세요.r&   )z0%(py3)s
{%(py3)s = %(py0)s(%(py1)s)
} == %(py5)sr   text)r!   r*   r"   r$   zassert %(py7)spy7N)	r   r/   r0   r1   r2   r3   r4   r5   r6   )r   r   r7   rw   r:   @py_format8s         r   #test_sanitize_normal_text_unchangedz5TestSanitizeTitle.test_sanitize_normal_text_unchanged	  s    /d#+#t++++#t++++++~+++~++++++d+++d+++#++++++t+++t+++++++r   N)r}   r~   r   r   r   r   r   r   r   r   r   r      s    1'!,r   r   )!r   
__future__r   builtinsr1   _pytest.assertion.rewrite	assertionrewriter/   r   sysr   timepathlibr   pytestpathinsertr	   __file__parentutils.session_storer   r   fixturer   r   r   r   r   r   r   r   r   r   r   <module>r      s    H "    
     3tH~,,33::; < <  :6 :6D$+ $+X   2= =:# #@, ,r   