Skip to content

Commit bad88e7

Browse files
feat(tests): add tests for unicode content, special characters, and concurrent access in SQLiteSession (#1399)
1 parent ce67311 commit bad88e7

File tree

1 file changed

+88
-0
lines changed

1 file changed

+88
-0
lines changed

tests/test_session.py

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)