Skip to content

Commit fd440c5

Browse files
feat(tests): add tests for unicode content, special characters, and concurrent access in SQLiteSession
1 parent 5539afc commit fd440c5

File tree

1 file changed

+83
-0
lines changed

1 file changed

+83
-0
lines changed

tests/test_session.py

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

Comments
 (0)