import pytest from classes.session_conversation_store import SessionConversationStore class TestSessionConversationStore: """Unit tests for SessionConversationStore class""" @pytest.fixture def store(self): """Fresh store instance for each test""" return SessionConversationStore() @pytest.fixture def sample_session_id(self): return "test-session-123" @pytest.fixture def sample_conversation_id(self): return "conversation-abc" # ==================== add_human_message Tests ==================== def test_add_human_message_new_session( self, store, sample_session_id, sample_conversation_id ): """Test adding a human message to a new session""" conversation = store.add_human_message( sample_session_id, sample_conversation_id, "Hello" ) assert sample_session_id in store.session_conversation_map assert ( sample_conversation_id in store.session_conversation_map[sample_session_id] ) assert len(conversation) == 1 assert conversation[0].role == "user" assert conversation[0].content == "Hello" def test_add_human_message_existing_session_new_conversation( self, store, sample_session_id ): """Test adding a human message to a new conversation in existing session""" conversation_id_1 = "conversation-1" conversation_id_2 = "conversation-2" conv1 = store.add_human_message( sample_session_id, conversation_id_1, "First conversation" ) conv2 = store.add_human_message( sample_session_id, conversation_id_2, "Second conversation" ) assert len(store.session_conversation_map[sample_session_id]) == 2 assert conversation_id_1 in store.session_conversation_map[sample_session_id] assert conversation_id_2 in store.session_conversation_map[sample_session_id] # Verify return values assert len(conv1) == 1 assert len(conv2) == 1 assert conv1[0].content == "First conversation" assert conv2[0].content == "Second conversation" def test_add_human_message_existing_conversation( self, store, sample_session_id, sample_conversation_id ): """Test adding multiple human messages to the same conversation""" conv1 = store.add_human_message( sample_session_id, sample_conversation_id, "First message" ) conv2 = store.add_human_message( sample_session_id, sample_conversation_id, "Second message" ) assert len(conv2) == 2 assert conv2[0].content == "First message" assert conv2[1].content == "Second message" def test_add_human_message_empty_string( self, store, sample_session_id, sample_conversation_id ): """Test adding an empty human message""" conversation = store.add_human_message( sample_session_id, sample_conversation_id, "" ) assert len(conversation) == 1 assert conversation[0].content == "" assert conversation[0].role == "user" def test_add_human_message_with_special_characters( self, store, sample_session_id, sample_conversation_id ): """Test adding a human message with special characters""" special_message = "Hello! @#$%^&*() 你好 🎉\n\tNew line" conversation = store.add_human_message( sample_session_id, sample_conversation_id, special_message ) assert conversation[0].content == special_message def test_add_human_message_very_long_text( self, store, sample_session_id, sample_conversation_id ): """Test adding a very long human message""" long_message = "x" * 10000 conversation = store.add_human_message( sample_session_id, sample_conversation_id, long_message ) assert conversation[0].content == long_message # ==================== add_assistant_reply Tests ==================== def test_add_assistant_reply_new_session( self, store, sample_session_id, sample_conversation_id ): """Test adding an assistant reply to a new session""" conversation = store.add_assistant_reply( sample_session_id, sample_conversation_id, "Hello!" ) assert sample_session_id in store.session_conversation_map assert ( sample_conversation_id in store.session_conversation_map[sample_session_id] ) assert len(conversation) == 1 assert conversation[0].role == "assistant" assert conversation[0].content == "Hello!" def test_add_assistant_reply_existing_session_new_conversation( self, store, sample_session_id ): """Test adding an assistant reply to a new conversation in existing session""" conversation_id_1 = "conversation-1" conversation_id_2 = "conversation-2" conv1 = store.add_assistant_reply( sample_session_id, conversation_id_1, "First reply" ) conv2 = store.add_assistant_reply( sample_session_id, conversation_id_2, "Second reply" ) assert len(store.session_conversation_map[sample_session_id]) == 2 assert len(conv1) == 1 assert len(conv2) == 1 def test_add_assistant_reply_existing_conversation( self, store, sample_session_id, sample_conversation_id ): """Test adding multiple assistant replies to the same conversation""" conv1 = store.add_assistant_reply( sample_session_id, sample_conversation_id, "First reply" ) conv2 = store.add_assistant_reply( sample_session_id, sample_conversation_id, "Second reply" ) assert len(conv2) == 2 assert conv2[0].content == "First reply" assert conv2[1].content == "Second reply" def test_add_assistant_reply_empty_string( self, store, sample_session_id, sample_conversation_id ): """Test adding an empty assistant reply""" conversation = store.add_assistant_reply( sample_session_id, sample_conversation_id, "" ) assert len(conversation) == 1 assert conversation[0].content == "" assert conversation[0].role == "assistant" def test_add_methods_return_same_reference( self, store, sample_session_id, sample_conversation_id ): """Test that returned conversation is the actual stored list (same reference)""" conv1 = store.add_human_message( sample_session_id, sample_conversation_id, "Message 1" ) conv2 = store.add_assistant_reply( sample_session_id, sample_conversation_id, "Reply 1" ) # Should be the same list object assert conv1 is conv2 assert ( conv2 is store.session_conversation_map[sample_session_id][sample_conversation_id] ) # Changes to returned list affect stored data assert len(conv2) == 2 # ==================== delete_session_conversations Tests ==================== def test_delete_session_conversations_existing_session( self, store, sample_session_id ): """Test deleting all conversations in a session""" store.add_human_message(sample_session_id, "conversation-1", "Message 1") store.add_human_message(sample_session_id, "conversation-2", "Message 2") store.delete_session_conversations(sample_session_id) assert sample_session_id not in store.session_conversation_map def test_delete_session_conversations_nonexistent_session(self, store): """Test deleting a nonexistent session (should not raise error)""" # Should not raise any exception store.delete_session_conversations("nonexistent-session") assert "nonexistent-session" not in store.session_conversation_map def test_delete_session_conversations_does_not_affect_other_sessions(self, store): """Test that deleting one session doesn't affect others""" session_1 = "session-1" session_2 = "session-2" store.add_human_message(session_1, "conversation-1", "Message 1") store.add_human_message(session_2, "conversation-2", "Message 2") store.delete_session_conversations(session_1) assert session_1 not in store.session_conversation_map assert session_2 in store.session_conversation_map assert "conversation-2" in store.session_conversation_map[session_2] def test_delete_session_conversations_with_multiple_conversations( self, store, sample_session_id ): """Test deleting a session with multiple conversations""" store.add_human_message(sample_session_id, "conversation-1", "Message 1") store.add_human_message(sample_session_id, "conversation-2", "Message 2") store.add_human_message(sample_session_id, "conversation-3", "Message 3") store.delete_session_conversations(sample_session_id) assert sample_session_id not in store.session_conversation_map # ==================== Integration Tests ==================== def test_conversation_flow_user_assistant_alternating( self, store, sample_session_id, sample_conversation_id ): """Test realistic conversation flow with alternating messages""" store.add_human_message( sample_session_id, sample_conversation_id, "What's the weather?" ) store.add_assistant_reply( sample_session_id, sample_conversation_id, "It's sunny today!" ) store.add_human_message(sample_session_id, sample_conversation_id, "Thanks!") conversation = store.add_assistant_reply( sample_session_id, sample_conversation_id, "You're welcome!" ) assert len(conversation) == 4 assert conversation[0].role == "user" assert conversation[1].role == "assistant" assert conversation[2].role == "user" assert conversation[3].role == "assistant" def test_multiple_conversations_in_same_session(self, store, sample_session_id): """Test multiple independent conversations in the same session""" conv1_id = "conversation-1" conv2_id = "conversation-2" # Conversation 1 store.add_human_message(sample_session_id, conv1_id, "Hello in conv 1") conv1 = store.add_assistant_reply( sample_session_id, conv1_id, "Reply in conv 1" ) # Conversation 2 store.add_human_message(sample_session_id, conv2_id, "Hello in conv 2") conv2 = store.add_assistant_reply( sample_session_id, conv2_id, "Reply in conv 2" ) assert len(conv1) == 2 assert len(conv2) == 2 assert conv1[0].content == "Hello in conv 1" assert conv2[0].content == "Hello in conv 2" def test_multiple_sessions_isolation(self, store): """Test that sessions are completely isolated""" session1 = "session-1" session2 = "session-2" conversation_id = "conversation-1" conv1 = store.add_human_message( session1, conversation_id, "Message in session 1" ) conv2 = store.add_human_message( session2, conversation_id, "Message in session 2" ) assert conv1[0].content == "Message in session 1" assert conv2[0].content == "Message in session 2" def test_conversation_order_preserved( self, store, sample_session_id, sample_conversation_id ): """Test that message order is preserved in the conversation""" messages = [f"Message {i}" for i in range(10)] conversation = None for i, msg in enumerate(messages): if i % 2 == 0: conversation = store.add_human_message( sample_session_id, sample_conversation_id, msg ) else: conversation = store.add_assistant_reply( sample_session_id, sample_conversation_id, msg ) assert len(conversation) == 10 for i, msg in enumerate(messages): assert conversation[i].content == msg def test_empty_session_after_deletion( self, store, sample_session_id, sample_conversation_id ): """Test that a session can be recreated after deletion""" # Create and delete store.add_human_message( sample_session_id, sample_conversation_id, "First message" ) store.delete_session_conversations(sample_session_id) # Recreate conversation = store.add_human_message( sample_session_id, sample_conversation_id, "New message" ) assert len(conversation) == 1 assert conversation[0].content == "New message" def test_chat_message_structure( self, store, sample_session_id, sample_conversation_id ): """Test that ChatMessage objects have correct structure""" conversation = store.add_human_message( sample_session_id, sample_conversation_id, "Test" ) message = conversation[0] assert hasattr(message, "role") assert hasattr(message, "content") assert message.role == "user" assert message.content == "Test" def test_consecutive_human_messages( self, store, sample_session_id, sample_conversation_id ): """Test adding multiple human messages without assistant replies""" store.add_human_message(sample_session_id, sample_conversation_id, "Message 1") store.add_human_message(sample_session_id, sample_conversation_id, "Message 2") conversation = store.add_human_message( sample_session_id, sample_conversation_id, "Message 3" ) assert len(conversation) == 3 assert all(msg.role == "user" for msg in conversation) def test_consecutive_assistant_messages( self, store, sample_session_id, sample_conversation_id ): """Test adding multiple assistant messages without human messages""" store.add_assistant_reply(sample_session_id, sample_conversation_id, "Reply 1") store.add_assistant_reply(sample_session_id, sample_conversation_id, "Reply 2") conversation = store.add_assistant_reply( sample_session_id, sample_conversation_id, "Reply 3" ) assert len(conversation) == 3 assert all(msg.role == "assistant" for msg in conversation) def test_large_conversation_history( self, store, sample_session_id, sample_conversation_id ): """Test handling a large conversation with many messages""" num_messages = 1000 conversation = None for i in range(num_messages): if i % 2 == 0: conversation = store.add_human_message( sample_session_id, sample_conversation_id, f"User message {i}" ) else: conversation = store.add_assistant_reply( sample_session_id, sample_conversation_id, f"Assistant message {i}" ) assert len(conversation) == num_messages def test_special_conversation_and_session_ids(self, store): """Test using special characters in IDs""" special_ids = [ ("session-with-dashes", "conversation-with-dashes"), ("session_with_underscores", "conversation_with_underscores"), ("session123", "conversation456"), ("a", "b"), # Single character IDs ] for session_id, conversation_id in special_ids: conversation = store.add_human_message( session_id, conversation_id, "Test message" ) assert len(conversation) == 1 def test_dict_structure_integrity(self, store): """Test that internal data structure maintains correct nesting""" session_id = "test-session" conv_id_1 = "conversation-1" conv_id_2 = "conversation-2" store.add_human_message(session_id, conv_id_1, "Message 1") store.add_human_message(session_id, conv_id_2, "Message 2") # Check structure assert isinstance(store.session_conversation_map, dict) assert isinstance(store.session_conversation_map[session_id], dict) assert isinstance(store.session_conversation_map[session_id][conv_id_1], list)