import pytest import time from unittest.mock import MagicMock, patch from integration.query_handler import QueryHandler, QueryMode, QueryResult, get_query_handler, reset_query_handler from agent import IntentType def _make_handler(mode=QueryMode.AUTO): return QueryHandler(default_mode=mode) class TestQueryResult: def test_success_when_no_error(self): r = QueryResult(query="test", response="ok", mode=QueryMode.DOCUMENTS) assert r.success is True def test_failure_when_error(self): r = QueryResult(query="test", response="fail", mode=QueryMode.DOCUMENTS, error="boom") assert r.success is False def test_to_dict_contains_all_keys(self): r = QueryResult(query="q", response="r", mode=QueryMode.DATABASE, sql_query="SELECT 1") d = r.to_dict() assert d["query"] == "q" assert d["mode"] == "database" assert d["sql_query"] == "SELECT 1" assert d["success"] is True class TestDecideExecution: @patch("integration.query_handler.DATABASE_CONFIG", {"enabled": True}) def test_database_mode_when_db_enabled(self): handler = _make_handler() assert handler._decide_execution(QueryMode.DATABASE, "anything") == "database" @patch("integration.query_handler.DATABASE_CONFIG", {"enabled": False}) def test_database_mode_falls_to_documents_when_disabled(self): handler = _make_handler() assert handler._decide_execution(QueryMode.DATABASE, "anything") == "documents" def test_documents_mode_always_documents(self): handler = _make_handler() assert handler._decide_execution(QueryMode.DOCUMENTS, "anything") == "documents" @patch("integration.query_handler.DATABASE_CONFIG", {"enabled": True}) def test_hybrid_with_db_signals(self): handler = _make_handler() assert handler._decide_execution(QueryMode.HYBRID, "26000046 statü") == "database" @patch("integration.query_handler.DATABASE_CONFIG", {"enabled": True}) def test_hybrid_without_db_signals(self): handler = _make_handler() assert handler._decide_execution(QueryMode.HYBRID, "merhaba nasılsın") == "documents" @patch("integration.query_handler.DATABASE_CONFIG", {"enabled": True}) @patch("integration.query_handler.get_intent_router") def test_auto_delegates_to_router(self, mock_router_fn): decision = MagicMock() decision.execution_mode = "database" router = MagicMock() router.route.return_value = decision mock_router_fn.return_value = router handler = _make_handler() result = handler._decide_execution(QueryMode.AUTO, "26000046 teklif") assert result == "database" router.route.assert_called_once() class TestExecuteDatabase: @patch("integration.query_handler.get_context_manager") @patch("integration.query_handler.get_query_rewriter") @patch("integration.query_handler.query_and_format") @patch("integration.query_handler.get_metrics") @patch("integration.query_handler.DATABASE_CONFIG", {"enabled": True}) def test_returns_db_result(self, mock_metrics, mock_qf, mock_rewriter, mock_ctx): mock_rewriter_inst = MagicMock() mock_rewriter_inst.rewrite.return_value = ("26000046 statü", {"method": "none"}) mock_rewriter.return_value = mock_rewriter_inst ctx = MagicMock() ctx.get_active_credit_file_id.return_value = None mock_ctx.return_value = ctx mock_qf.return_value = { "success": True, "response": "26000046 teklifinin statüsü: Teklif.", "sql": "SELECT * FROM v_CurrentCreditFileStatus WHERE CreditFileId = 26000046", "rows": [{"CreditFileId": 26000046, "StatusName": "Teklif"}], } mock_metrics.return_value = MagicMock() handler = _make_handler() result = handler.execute("26000046 statü", mode=QueryMode.DATABASE) assert result.mode == QueryMode.DATABASE assert result.sql_query is not None assert result.db_results is not None assert "Teklif" in result.response assert result.intent == IntentType.DATABASE_QUERY.value class TestExecuteDocuments: @patch("integration.query_handler.get_context_manager") @patch("integration.query_handler.get_query_rewriter") @patch("integration.query_handler.get_retrieval_engine") @patch("integration.query_handler.get_llm_generator") @patch("integration.query_handler.get_metrics") @patch("integration.query_handler.DATABASE_CONFIG", {"enabled": False}) def test_returns_document_result(self, mock_metrics, mock_gen, mock_engine, mock_rewriter, mock_ctx): mock_rewriter_inst = MagicMock() mock_rewriter_inst.rewrite.return_value = ("Atlas nedir", {"method": "none"}) mock_rewriter.return_value = mock_rewriter_inst ctx = MagicMock() ctx.get_active_credit_file_id.return_value = None mock_ctx.return_value = ctx engine = MagicMock() engine.retrieve.return_value = ([{"text": "Atlas bir sistemdir", "metadata": {}, "source": "doc.pdf", "score": 0.9}], {}) engine.build_context.return_value = "Atlas bir sistemdir" mock_engine.return_value = engine generator = MagicMock() generator.generate.return_value = "Atlas, kredi süreçlerini yöneten bir sistemdir." mock_gen.return_value = generator mock_metrics.return_value = MagicMock() handler = _make_handler() result = handler.execute("Atlas nedir", mode=QueryMode.DOCUMENTS) assert result.mode == QueryMode.DOCUMENTS assert result.intent == IntentType.DOCUMENT_QUERY.value assert len(result.sources) == 1 assert result.error is None class TestExecuteConversational: @patch("integration.query_handler.get_context_manager") @patch("integration.query_handler.get_query_rewriter") @patch("integration.query_handler.get_intent_router") @patch("integration.query_handler.get_llm_generator") @patch("integration.query_handler.get_metrics") @patch("integration.query_handler.DATABASE_CONFIG", {"enabled": False}) def test_greeting_goes_conversational(self, mock_metrics, mock_gen, mock_router_fn, mock_rewriter, mock_ctx): mock_rewriter_inst = MagicMock() mock_rewriter_inst.rewrite.return_value = ("merhaba", {"method": "none"}) mock_rewriter.return_value = mock_rewriter_inst ctx = MagicMock() ctx.get_active_credit_file_id.return_value = None mock_ctx.return_value = ctx decision = MagicMock() decision.execution_mode = "conversational" router = MagicMock() router.route.return_value = decision mock_router_fn.return_value = router generator = MagicMock() generator.generate.return_value = "Merhaba! Size nasıl yardımcı olabilirim?" mock_gen.return_value = generator mock_metrics.return_value = MagicMock() handler = _make_handler() result = handler.execute("merhaba", mode=QueryMode.AUTO) assert result.intent == IntentType.CONVERSATIONAL.value assert "Merhaba" in result.response class TestHybridMode: @patch("integration.query_handler.get_context_manager") @patch("integration.query_handler.get_query_rewriter") @patch("integration.query_handler.query_and_format") @patch("integration.query_handler.get_metrics") @patch("integration.query_handler.DATABASE_CONFIG", {"enabled": True}) def test_hybrid_routes_to_db_with_signals(self, mock_metrics, mock_qf, mock_rewriter, mock_ctx): mock_rewriter_inst = MagicMock() mock_rewriter_inst.rewrite.return_value = ("26000046 statü", {"method": "none"}) mock_rewriter.return_value = mock_rewriter_inst ctx = MagicMock() ctx.get_active_credit_file_id.return_value = None mock_ctx.return_value = ctx mock_qf.return_value = { "success": True, "response": "26000046 teklifinin statüsü: Sözleşme ve Ekleri Hazır.", "sql": "SELECT ...", "rows": [{"CreditFileId": 26000046, "StatusName": "Sözleşme ve Ekleri Hazır"}], } mock_metrics.return_value = MagicMock() handler = _make_handler() result = handler.execute("26000046 statü", mode=QueryMode.HYBRID) assert result.mode == QueryMode.DATABASE assert "Sözleşme ve Ekleri Hazır" in result.response @patch("integration.query_handler.get_context_manager") @patch("integration.query_handler.get_query_rewriter") @patch("integration.query_handler.get_retrieval_engine") @patch("integration.query_handler.get_llm_generator") @patch("integration.query_handler.get_metrics") @patch("integration.query_handler.DATABASE_CONFIG", {"enabled": True}) def test_hybrid_falls_to_docs_without_signals(self, mock_metrics, mock_gen, mock_engine, mock_rewriter, mock_ctx): mock_rewriter_inst = MagicMock() mock_rewriter_inst.rewrite.return_value = ("Atlas nedir", {"method": "none"}) mock_rewriter.return_value = mock_rewriter_inst ctx = MagicMock() ctx.get_active_credit_file_id.return_value = None mock_ctx.return_value = ctx engine = MagicMock() engine.retrieve.return_value = ([], {}) engine.build_context.return_value = "" mock_engine.return_value = engine generator = MagicMock() generator.generate.return_value = "Atlas bir sistemdir." mock_gen.return_value = generator mock_metrics.return_value = MagicMock() handler = _make_handler() result = handler.execute("Atlas nedir", mode=QueryMode.HYBRID) assert result.mode == QueryMode.DOCUMENTS @patch("integration.query_handler.get_context_manager") @patch("integration.query_handler.get_query_rewriter") @patch("integration.query_handler.query_and_format") @patch("integration.query_handler.get_metrics") @patch("integration.query_handler.DATABASE_CONFIG", {"enabled": True}) def test_hybrid_no_rows_returns_not_found(self, mock_metrics, mock_qf, mock_rewriter, mock_ctx): mock_rewriter_inst = MagicMock() mock_rewriter_inst.rewrite.return_value = ("99999999 statü", {"method": "none"}) mock_rewriter.return_value = mock_rewriter_inst ctx = MagicMock() ctx.get_active_credit_file_id.return_value = None mock_ctx.return_value = ctx mock_qf.return_value = { "success": True, "response": "99999999 numaralı teklif için veritabanında kayıt bulunamadı.", "sql": "SELECT ...", "rows": [], } mock_metrics.return_value = MagicMock() handler = _make_handler() result = handler.execute("99999999 statü", mode=QueryMode.HYBRID) assert result.db_results is not None assert len(result.db_results) == 0 assert "bulunamadı" in result.response class TestErrorHandling: @patch("integration.query_handler.get_context_manager") @patch("integration.query_handler.get_query_rewriter") @patch("integration.query_handler.query_and_format") @patch("integration.query_handler.get_metrics") @patch("integration.query_handler.DATABASE_CONFIG", {"enabled": True}) def test_db_error_surfaced(self, mock_metrics, mock_qf, mock_rewriter, mock_ctx): mock_rewriter_inst = MagicMock() mock_rewriter_inst.rewrite.return_value = ("26000046 statü", {"method": "none"}) mock_rewriter.return_value = mock_rewriter_inst ctx = MagicMock() ctx.get_active_credit_file_id.return_value = None mock_ctx.return_value = ctx mock_qf.return_value = { "success": False, "response": "Hata oluştu", "sql": "SELECT ...", "rows": None, "error": "connection refused", } mock_metrics.return_value = MagicMock() handler = _make_handler() result = handler.execute("26000046 statü", mode=QueryMode.DATABASE) assert result.error is not None assert "connection refused" in result.error @patch("integration.query_handler.get_context_manager") @patch("integration.query_handler.get_query_rewriter") @patch("integration.query_handler.get_retrieval_engine") @patch("integration.query_handler.get_metrics") @patch("integration.query_handler.DATABASE_CONFIG", {"enabled": False}) def test_document_retrieval_error(self, mock_metrics, mock_engine, mock_rewriter, mock_ctx): mock_rewriter_inst = MagicMock() mock_rewriter_inst.rewrite.return_value = ("test", {"method": "none"}) mock_rewriter.return_value = mock_rewriter_inst ctx = MagicMock() ctx.get_active_credit_file_id.return_value = None mock_ctx.return_value = ctx engine = MagicMock() engine.retrieve.side_effect = Exception("retrieval failed") mock_engine.return_value = engine mock_metrics.return_value = MagicMock() handler = _make_handler() result = handler.execute("test", mode=QueryMode.DOCUMENTS) assert result.error is not None assert "retrieval failed" in result.error class TestQueryHistory: def test_starts_empty(self): handler = _make_handler() assert handler.query_history == [] @patch("integration.query_handler.get_context_manager") @patch("integration.query_handler.get_query_rewriter") @patch("integration.query_handler.query_and_format") @patch("integration.query_handler.get_metrics") @patch("integration.query_handler.DATABASE_CONFIG", {"enabled": True}) def test_records_after_execute(self, mock_metrics, mock_qf, mock_rewriter, mock_ctx): mock_rewriter_inst = MagicMock() mock_rewriter_inst.rewrite.return_value = ("26000046 statü", {"method": "none"}) mock_rewriter.return_value = mock_rewriter_inst ctx = MagicMock() ctx.get_active_credit_file_id.return_value = None mock_ctx.return_value = ctx mock_qf.return_value = {"success": True, "response": "ok", "sql": "SELECT 1", "rows": []} mock_metrics.return_value = MagicMock() handler = _make_handler() handler.execute("26000046 statü", mode=QueryMode.DATABASE) assert len(handler.query_history) == 1 def test_clear_history(self): handler = _make_handler() handler._query_history.append(QueryResult(query="q", response="r", mode=QueryMode.DOCUMENTS)) handler.clear_history() assert len(handler.query_history) == 0 class TestSingleton: def setup_method(self): reset_query_handler() def teardown_method(self): reset_query_handler() def test_returns_same_instance(self): h1 = get_query_handler() h2 = get_query_handler() assert h1 is h2 def test_reset_creates_new(self): h1 = get_query_handler() reset_query_handler() h2 = get_query_handler() assert h1 is not h2 def test_mode_update(self): h1 = get_query_handler(default_mode=QueryMode.DOCUMENTS) assert h1.default_mode == QueryMode.DOCUMENTS get_query_handler(default_mode=QueryMode.HYBRID) assert h1.default_mode == QueryMode.HYBRID