| """ |
| Unit tests for app module |
| Tests main orchestration logic |
| """ |
|
|
| import unittest |
| from unittest.mock import MagicMock, Mock, patch |
|
|
| from app import get_context_and_answer |
|
|
|
|
| class TestApp(unittest.TestCase): |
| """Test cases for app module""" |
|
|
| def setUp(self): |
| """Set up test fixtures""" |
| self.message = "How do I create an account?" |
| self.history = [["Previous question", "Previous answer"]] |
| self.session_id = "test-session-123" |
| self.mock_intent_classifier = Mock() |
| self.mock_retriever = Mock() |
|
|
| @patch("app.log_timing_data") |
| @patch("app.log_response") |
| @patch("app.update_memory") |
| @patch("app.retrieve_memory") |
| @patch("app.create_session_config") |
| def test_get_context_and_answer_simple_intent( |
| self, |
| mock_session_config, |
| mock_retrieve_memory, |
| mock_update_memory, |
| mock_log_response, |
| mock_log_timing, |
| ): |
| """Test get_context_and_answer with simple intent (greeting)""" |
| |
| mock_session_config.return_value = {"session_id": self.session_id} |
| mock_retrieve_memory.return_value = [] |
| self.mock_intent_classifier.classify_intent.return_value = ( |
| "greeting", |
| "Hello! How can I help you?", |
| ) |
|
|
| |
| answer = get_context_and_answer( |
| "Hello", |
| self.history, |
| self.session_id, |
| self.mock_intent_classifier, |
| self.mock_retriever, |
| ) |
|
|
| |
| self.mock_intent_classifier.classify_intent.assert_called_once_with("Hello") |
|
|
| |
| self.mock_retriever.invoke.assert_not_called() |
|
|
| |
| self.assertEqual(answer, "Hello! How can I help you?") |
|
|
| |
| mock_update_memory.assert_called_once() |
|
|
| |
| mock_log_response.assert_called_once() |
| mock_log_timing.assert_called_once() |
|
|
| @patch("app.generate_xeno_response") |
| @patch("app.process_context") |
| @patch("app.generate_embeddings") |
| @patch("app.log_timing_data") |
| @patch("app.log_response") |
| @patch("app.update_memory") |
| @patch("app.retrieve_memory") |
| @patch("app.create_session_config") |
| def test_get_context_and_answer_query_intent( |
| self, |
| mock_session_config, |
| mock_retrieve_memory, |
| mock_update_memory, |
| mock_log_response, |
| mock_log_timing, |
| mock_generate_embeddings, |
| mock_process_context, |
| mock_generate_response, |
| ): |
| """Test get_context_and_answer with query intent""" |
| |
| mock_session_config.return_value = {"session_id": self.session_id} |
| mock_retrieve_memory.return_value = [] |
| self.mock_intent_classifier.classify_intent.return_value = ("query", None) |
|
|
| |
| mock_doc = Mock() |
| mock_doc.page_content = "Test content" |
| mock_doc.metadata = {"id": "KB001", "question": "Q", "content": "A"} |
| self.mock_retriever.invoke.return_value = [mock_doc] |
|
|
| |
| mock_generate_embeddings.return_value = ( |
| [0.1, 0.2, 0.3], |
| [[0.2, 0.3, 0.4]], |
| ) |
|
|
| |
| mock_process_context.return_value = ( |
| "Formatted context", |
| ["KB001"], |
| [("Q", "A")], |
| ) |
|
|
| |
| mock_generate_response.return_value = "Generated answer" |
|
|
| |
| answer = get_context_and_answer( |
| self.message, |
| self.history, |
| self.session_id, |
| self.mock_intent_classifier, |
| self.mock_retriever, |
| ) |
|
|
| |
| self.mock_retriever.invoke.assert_called_once_with(self.message) |
| mock_generate_embeddings.assert_called_once() |
| mock_process_context.assert_called_once() |
| mock_generate_response.assert_called_once() |
|
|
| |
| self.assertEqual(answer, "Generated answer") |
|
|
| |
| mock_log_response.assert_called_once() |
| mock_log_timing.assert_called_once() |
|
|
| @patch("app.log_timing_data") |
| @patch("app.log_response") |
| @patch("app.update_memory") |
| @patch("app.retrieve_memory") |
| @patch("app.create_session_config") |
| def test_get_context_and_answer_short_message( |
| self, |
| mock_session_config, |
| mock_retrieve_memory, |
| mock_update_memory, |
| mock_log_response, |
| mock_log_timing, |
| ): |
| """Test get_context_and_answer with very short message""" |
| |
| mock_session_config.return_value = {"session_id": self.session_id} |
| mock_retrieve_memory.return_value = [] |
| self.mock_intent_classifier.classify_intent.return_value = ("query", None) |
|
|
| |
| answer = get_context_and_answer( |
| "Hi", |
| self.history, |
| self.session_id, |
| self.mock_intent_classifier, |
| self.mock_retriever, |
| ) |
|
|
| |
| self.assertIn("more details", answer) |
|
|
| |
| self.mock_retriever.invoke.assert_not_called() |
|
|
| @patch("app.generate_embeddings") |
| @patch("app.log_timing_data") |
| @patch("app.log_response") |
| @patch("app.update_memory") |
| @patch("app.retrieve_memory") |
| @patch("app.create_session_config") |
| def test_get_context_and_answer_low_similarity( |
| self, |
| mock_session_config, |
| mock_retrieve_memory, |
| mock_update_memory, |
| mock_log_response, |
| mock_log_timing, |
| mock_generate_embeddings, |
| ): |
| """Test get_context_and_answer with low similarity score""" |
| |
| mock_session_config.return_value = {"session_id": self.session_id} |
| mock_retrieve_memory.return_value = [] |
| self.mock_intent_classifier.classify_intent.return_value = ("query", None) |
|
|
| |
| mock_doc = Mock() |
| mock_doc.page_content = "Test content" |
| self.mock_retriever.invoke.return_value = [mock_doc] |
|
|
| |
| mock_generate_embeddings.return_value = ( |
| [0.1, 0.2, 0.3], |
| [[1.0, 0.0, 0.0]], |
| ) |
|
|
| |
| answer = get_context_and_answer( |
| "Some random question", |
| self.history, |
| self.session_id, |
| self.mock_intent_classifier, |
| self.mock_retriever, |
| ) |
|
|
| |
| self.assertIn("couldn't find", answer) |
|
|
| @patch("app.log_timing_data") |
| @patch("app.log_response") |
| @patch("app.update_memory") |
| @patch("app.retrieve_memory") |
| @patch("app.create_session_config") |
| def test_get_context_and_answer_rag_error( |
| self, |
| mock_session_config, |
| mock_retrieve_memory, |
| mock_update_memory, |
| mock_log_response, |
| mock_log_timing, |
| ): |
| """Test get_context_and_answer handles RAG errors gracefully""" |
| |
| mock_session_config.return_value = {"session_id": self.session_id} |
| mock_retrieve_memory.return_value = [] |
| self.mock_intent_classifier.classify_intent.return_value = ("query", None) |
|
|
| |
| self.mock_retriever.invoke.side_effect = Exception("Database error") |
|
|
| |
| answer = get_context_and_answer( |
| self.message, |
| self.history, |
| self.session_id, |
| self.mock_intent_classifier, |
| self.mock_retriever, |
| ) |
|
|
| |
| self.assertIn("technical issue", answer) |
|
|
| |
| mock_log_timing.assert_called_once() |
| call_kwargs = mock_log_timing.call_args[1] |
| self.assertIsNotNone(call_kwargs.get("error_step")) |
|
|
| @patch("app.log_timing_data") |
| @patch("app.update_memory") |
| @patch("app.retrieve_memory") |
| @patch("app.create_session_config") |
| def test_get_context_and_answer_main_error( |
| self, |
| mock_session_config, |
| mock_retrieve_memory, |
| mock_update_memory, |
| mock_log_timing, |
| ): |
| """Test get_context_and_answer handles main pipeline errors""" |
| |
| mock_session_config.return_value = {"session_id": self.session_id} |
| mock_retrieve_memory.side_effect = Exception("Memory error") |
|
|
| |
| answer = get_context_and_answer( |
| self.message, |
| self.history, |
| self.session_id, |
| self.mock_intent_classifier, |
| self.mock_retriever, |
| ) |
|
|
| |
| self.assertIn("error", answer) |
|
|
| |
| mock_log_timing.assert_called_once() |
|
|
| @patch("app.generate_xeno_response") |
| @patch("app.process_context") |
| @patch("app.generate_embeddings") |
| @patch("app.log_timing_data") |
| @patch("app.log_response") |
| @patch("app.update_memory") |
| @patch("app.retrieve_memory") |
| @patch("app.create_session_config") |
| def test_get_context_and_answer_with_chat_history( |
| self, |
| mock_session_config, |
| mock_retrieve_memory, |
| mock_update_memory, |
| mock_log_response, |
| mock_log_timing, |
| mock_generate_embeddings, |
| mock_process_context, |
| mock_generate_response, |
| ): |
| """Test get_context_and_answer passes chat history to LLM""" |
| |
| mock_session_config.return_value = {"session_id": self.session_id} |
| chat_history = [ |
| {"role": "user", "content": "Previous question"}, |
| {"role": "assistant", "content": "Previous answer"}, |
| ] |
| mock_retrieve_memory.return_value = chat_history |
| self.mock_intent_classifier.classify_intent.return_value = ("query", None) |
|
|
| |
| mock_doc = Mock() |
| mock_doc.page_content = "Test content" |
| mock_doc.metadata = {"id": "KB001", "question": "Q", "content": "A"} |
| self.mock_retriever.invoke.return_value = [mock_doc] |
|
|
| |
| mock_generate_embeddings.return_value = ([0.1, 0.2], [[0.9, 0.1]]) |
|
|
| |
| mock_process_context.return_value = ("Context", ["KB001"], [("Q", "A")]) |
|
|
| |
| mock_generate_response.return_value = "Answer with context" |
|
|
| |
| answer = get_context_and_answer( |
| self.message, |
| self.history, |
| self.session_id, |
| self.mock_intent_classifier, |
| self.mock_retriever, |
| ) |
|
|
| |
| mock_generate_response.assert_called_once() |
| call_args = mock_generate_response.call_args[0] |
| self.assertEqual(call_args[2], chat_history) |
|
|
| @patch("app.PipelineTimer") |
| @patch("app.generate_xeno_response") |
| @patch("app.process_context") |
| @patch("app.generate_embeddings") |
| @patch("app.log_timing_data") |
| @patch("app.log_response") |
| @patch("app.update_memory") |
| @patch("app.retrieve_memory") |
| @patch("app.create_session_config") |
| def test_get_context_and_answer_timing( |
| self, |
| mock_session_config, |
| mock_retrieve_memory, |
| mock_update_memory, |
| mock_log_response, |
| mock_log_timing, |
| mock_generate_embeddings, |
| mock_process_context, |
| mock_generate_response, |
| mock_timer_class, |
| ): |
| """Test get_context_and_answer uses PipelineTimer correctly""" |
| |
| mock_timer = Mock() |
| mock_timer.time_step = MagicMock() |
| mock_timer.time_step.return_value.__enter__ = Mock() |
| mock_timer.time_step.return_value.__exit__ = Mock() |
| mock_timer.get_timing_summary.return_value = {"total": 1.5} |
| mock_timer_class.return_value = mock_timer |
|
|
| mock_session_config.return_value = {"session_id": self.session_id} |
| mock_retrieve_memory.return_value = [] |
| self.mock_intent_classifier.classify_intent.return_value = ("query", None) |
|
|
| |
| mock_doc = Mock() |
| mock_doc.page_content = "Test" |
| mock_doc.metadata = {"id": "KB001", "question": "Q", "content": "A"} |
| self.mock_retriever.invoke.return_value = [mock_doc] |
|
|
| |
| mock_generate_embeddings.return_value = ([0.1], [[0.9]]) |
| mock_process_context.return_value = ("Context", ["KB001"], [("Q", "A")]) |
| mock_generate_response.return_value = "Answer" |
|
|
| |
| get_context_and_answer( |
| self.message, |
| self.history, |
| self.session_id, |
| self.mock_intent_classifier, |
| self.mock_retriever, |
| ) |
|
|
| |
| mock_timer.reset.assert_called_once() |
| mock_timer.get_timing_summary.assert_called() |
|
|
| |
| mock_log_timing.assert_called_once() |
| call_args = mock_log_timing.call_args[0] |
| |
| self.assertIn("total", call_args[2]) |
|
|
|
|
| if __name__ == "__main__": |
| unittest.main() |
|
|