@@ -398,3 +398,91 @@ async def test_session_memory_rejects_both_session_and_list_input(runner_method)
398
398
assert "manually manage conversation history" in str (exc_info .value )
399
399
400
400
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