@@ -398,3 +398,86 @@ async def test_session_memory_rejects_both_session_and_list_input(runner_method)
398398 assert "manually manage conversation history" in str (exc_info .value )
399399
400400 session .close ()
401+
402+ @pytest .mark .asyncio
403+ async def test_sqlite_session_unicode_content ():
404+ """Test that session correctly stores and retrieves unicode/non-ASCII content."""
405+ with tempfile .TemporaryDirectory () as temp_dir :
406+ db_path = Path (temp_dir ) / "test_unicode.db"
407+ session_id = "unicode_test"
408+ session = SQLiteSession (session_id , db_path )
409+
410+ # Add unicode content to the session
411+ items : list [TResponseInputItem ] = [
412+ {"role" : "user" , "content" : "こんにちは" },
413+ {"role" : "assistant" , "content" : "😊👍" },
414+ {"role" : "user" , "content" : "Привет" },
415+ ]
416+ await session .add_items (items )
417+
418+ # Retrieve items and verify unicode content
419+ retrieved = await session .get_items ()
420+ assert retrieved [0 ].get ("content" ) == "こんにちは"
421+ assert retrieved [1 ].get ("content" ) == "😊👍"
422+ assert retrieved [2 ].get ("content" ) == "Привет"
423+ session .close ()
424+
425+
426+ @pytest .mark .asyncio
427+ async def test_sqlite_session_special_characters_and_sql_injection ():
428+ """
429+ Test that session safely stores and retrieves items with special characters and SQL keywords.
430+ """
431+ with tempfile .TemporaryDirectory () as temp_dir :
432+ db_path = Path (temp_dir ) / "test_special_chars.db"
433+ session_id = "special_chars_test"
434+ session = SQLiteSession (session_id , db_path )
435+
436+ # Add items with special characters and SQL keywords
437+ items : list [TResponseInputItem ] = [
438+ {"role" : "user" , "content" : "O'Reilly" },
439+ {"role" : "assistant" , "content" : "DROP TABLE sessions;" },
440+ {"role" : "user" , "content" : (
441+ '"SELECT * FROM users WHERE name = \" admin\" ;"'
442+ )},
443+ {"role" : "assistant" , "content" : "Robert'); DROP TABLE students;--" },
444+ {"role" : "user" , "content" : "Normal message" },
445+ ]
446+ await session .add_items (items )
447+ retrieved = await session .get_items ()
448+ assert len (retrieved ) == len (items )
449+ for i , item in enumerate (items ):
450+ assert retrieved [i ].get ("content" ) == item ["content" ]
451+ session .close ()
452+
453+ @pytest .mark .asyncio
454+ async def test_sqlite_session_concurrent_access ():
455+ """
456+ Test concurrent access to the same session to verify data integrity.
457+ """
458+ import concurrent .futures
459+ with tempfile .TemporaryDirectory () as temp_dir :
460+ db_path = Path (temp_dir ) / "test_concurrent.db"
461+ session_id = "concurrent_test"
462+ session = SQLiteSession (session_id , db_path )
463+
464+ # Add initial item
465+ items : list [TResponseInputItem ] = [
466+ {"role" : "user" , "content" : f"Message { i } " } for i in range (10 )
467+ ]
468+
469+ # Use ThreadPoolExecutor to simulate concurrent writes
470+ def add_item (item ):
471+ loop = asyncio .new_event_loop ()
472+ asyncio .set_event_loop (loop )
473+ loop .run_until_complete (session .add_items ([item ]))
474+ loop .close ()
475+ with concurrent .futures .ThreadPoolExecutor (max_workers = 5 ) as executor :
476+ executor .map (add_item , items )
477+
478+ # Retrieve all items and verify all are present
479+ retrieved = await session .get_items ()
480+ contents = {item .get ("content" ) for item in retrieved }
481+ expected = {f"Message { i } " for i in range (10 )}
482+ assert contents == expected
483+ session .close ()
0 commit comments