@@ -398,3 +398,91 @@ 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+
448+ # Retrieve all items and verify they are stored correctly
449+ retrieved = await session .get_items ()
450+ assert len (retrieved ) == len (items )
451+ assert retrieved [0 ].get ("content" ) == "O'Reilly"
452+ assert retrieved [1 ].get ("content" ) == "DROP TABLE sessions;"
453+ assert retrieved [2 ].get ("content" ) == '"SELECT * FROM users WHERE name = \" admin\" ;"'
454+ assert retrieved [3 ].get ("content" ) == "Robert'); DROP TABLE students;--"
455+ assert retrieved [4 ].get ("content" ) == "Normal message"
456+ session .close ()
457+
458+ @pytest .mark .asyncio
459+ async def test_sqlite_session_concurrent_access ():
460+ """
461+ Test concurrent access to the same session to verify data integrity.
462+ """
463+ import concurrent .futures
464+ with tempfile .TemporaryDirectory () as temp_dir :
465+ db_path = Path (temp_dir ) / "test_concurrent.db"
466+ session_id = "concurrent_test"
467+ session = SQLiteSession (session_id , db_path )
468+
469+ # Add initial item
470+ items : list [TResponseInputItem ] = [
471+ {"role" : "user" , "content" : f"Message { i } " } for i in range (10 )
472+ ]
473+
474+ # Use ThreadPoolExecutor to simulate concurrent writes
475+ def add_item (item ):
476+ loop = asyncio .new_event_loop ()
477+ asyncio .set_event_loop (loop )
478+ loop .run_until_complete (session .add_items ([item ]))
479+ loop .close ()
480+ with concurrent .futures .ThreadPoolExecutor (max_workers = 5 ) as executor :
481+ executor .map (add_item , items )
482+
483+ # Retrieve all items and verify all are present
484+ retrieved = await session .get_items ()
485+ contents = {item .get ("content" ) for item in retrieved }
486+ expected = {f"Message { i } " for i in range (10 )}
487+ assert contents == expected
488+ session .close ()
0 commit comments