# Tests for intent classifier import pytest from unittest.mock import Mock, patch, MagicMock from src.agent.intent_classifier import ( IntentClassifier, IntentType, ClassificationResult, get_intent_classifier, reset_intent_classifier, ) class TestIntentType: # Tests for IntentType enum def test_intent_types_exist(self): assert IntentType.DOCUMENT_QUERY is not None assert IntentType.DATABASE_QUERY is not None assert IntentType.HYBRID_QUERY is not None assert IntentType.CONVERSATIONAL is not None assert IntentType.AMBIGUOUS is not None def test_intent_type_values(self): assert IntentType.DOCUMENT_QUERY.value == "document_query" assert IntentType.DATABASE_QUERY.value == "database_query" assert IntentType.HYBRID_QUERY.value == "hybrid_query" assert IntentType.CONVERSATIONAL.value == "conversational" assert IntentType.AMBIGUOUS.value == "ambiguous" class TestClassificationResult: # Tests for ClassificationResult dataclass def test_classification_result_creation(self): result = ClassificationResult( intent=IntentType.DATABASE_QUERY, confidence=0.95, reasoning="Test reasoning", suggested_tables=["users", "orders"], ) assert result.intent == IntentType.DATABASE_QUERY assert result.confidence == 0.95 assert result.reasoning == "Test reasoning" assert result.suggested_tables == ["users", "orders"] def test_classification_result_default_values(self): result = ClassificationResult( intent=IntentType.DOCUMENT_QUERY, confidence=0.8, reasoning="Default test", ) assert result.suggested_tables == [] assert result.requires_clarification is False def test_classification_result_to_dict(self): result = ClassificationResult( intent=IntentType.HYBRID_QUERY, confidence=0.85, reasoning="Both sources needed", suggested_tables=["products"], ) data = result.to_dict() assert data["intent"] == "hybrid_query" assert data["confidence"] == 0.85 assert data["reasoning"] == "Both sources needed" assert data["suggested_tables"] == ["products"] def test_is_database_related(self): db_result = ClassificationResult(IntentType.DATABASE_QUERY, 0.9, "test") hybrid_result = ClassificationResult(IntentType.HYBRID_QUERY, 0.8, "test") doc_result = ClassificationResult(IntentType.DOCUMENT_QUERY, 0.9, "test") assert db_result.is_database_related() is True assert hybrid_result.is_database_related() is True assert doc_result.is_database_related() is False def test_is_document_related(self): doc_result = ClassificationResult(IntentType.DOCUMENT_QUERY, 0.9, "test") hybrid_result = ClassificationResult(IntentType.HYBRID_QUERY, 0.8, "test") db_result = ClassificationResult(IntentType.DATABASE_QUERY, 0.9, "test") assert doc_result.is_document_related() is True assert hybrid_result.is_document_related() is True assert db_result.is_document_related() is False class TestIntentClassifier: # Tests for IntentClassifier class @pytest.fixture def classifier(self): with patch("src.agent.intent_classifier.Groq") as mock_groq_class: mock_client = Mock() mock_groq_class.return_value = mock_client classifier = IntentClassifier(api_key="test_key") classifier._client = mock_client return classifier def test_classifier_initialization(self): classifier = IntentClassifier(api_key="test", confidence_threshold=0.8) assert classifier._confidence_threshold == 0.8 def test_classifier_with_custom_threshold(self): classifier = IntentClassifier( api_key="test", confidence_threshold=0.5, ) assert classifier._confidence_threshold == 0.5 def test_classify_database_query(self, classifier): mock_response = Mock() mock_response.choices = [ Mock(message=Mock(content='{"intent": "DATABASE_QUERY", "confidence": 0.9, "reasoning": "SQL needed", "suggested_tables": ["musteriler"]}')) ] classifier._client.chat.completions.create.return_value = mock_response result = classifier.classify("Kac tane musterimiz var?") assert result.intent == IntentType.DATABASE_QUERY assert result.confidence == 0.9 assert "musteriler" in result.suggested_tables def test_classify_document_query(self, classifier): mock_response = Mock() mock_response.choices = [ Mock(message=Mock(content='{"intent": "DOCUMENT_QUERY", "confidence": 0.85, "reasoning": "Document search", "suggested_tables": []}')) ] classifier._client.chat.completions.create.return_value = mock_response result = classifier.classify("Sirketi kim kurdu?") assert result.intent == IntentType.DOCUMENT_QUERY def test_classify_hybrid_query(self, classifier): mock_response = Mock() mock_response.choices = [ Mock(message=Mock(content='{"intent": "HYBRID_QUERY", "confidence": 0.75, "reasoning": "Both needed", "suggested_tables": ["urunler"]}')) ] classifier._client.chat.completions.create.return_value = mock_response result = classifier.classify("Urun politikamiz nedir ve kac urunumuz var?") assert result.intent == IntentType.HYBRID_QUERY def test_classify_conversational(self, classifier): mock_response = Mock() mock_response.choices = [ Mock(message=Mock(content='{"intent": "CONVERSATIONAL", "confidence": 0.95, "reasoning": "Greeting", "suggested_tables": []}')) ] classifier._client.chat.completions.create.return_value = mock_response result = classifier.classify("Merhaba!") assert result.intent == IntentType.CONVERSATIONAL def test_classify_with_context(self, classifier): mock_response = Mock() mock_response.choices = [ Mock(message=Mock(content='{"intent": "DATABASE_QUERY", "confidence": 0.88, "reasoning": "With context", "suggested_tables": []}')) ] classifier._client.chat.completions.create.return_value = mock_response chat_history = [{"role": "user", "content": "previous query"}] result = classifier.classify("Devam et", chat_history) assert result is not None classifier._client.chat.completions.create.assert_called_once() def test_classify_invalid_json_response(self, classifier): mock_response = Mock() mock_response.choices = [Mock(message=Mock(content="Invalid JSON"))] classifier._client.chat.completions.create.return_value = mock_response result = classifier.classify("Test query") # Implementation defaults to DOCUMENT_QUERY on parse failure assert result.intent == IntentType.DOCUMENT_QUERY assert result.confidence == 0.5 def test_classify_api_error(self, classifier): classifier._client.chat.completions.create.side_effect = Exception("API Error") result = classifier.classify("Test query") # Implementation defaults to DOCUMENT_QUERY on API error assert result.intent == IntentType.DOCUMENT_QUERY assert result.confidence == 0.5 class TestIntentClassifierSingleton: # Tests for singleton pattern def setup_method(self): reset_intent_classifier() def teardown_method(self): reset_intent_classifier() @patch("src.agent.intent_classifier.Groq") def test_get_intent_classifier_creates_instance(self, mock_groq): classifier = get_intent_classifier() assert classifier is not None assert isinstance(classifier, IntentClassifier) @patch("src.agent.intent_classifier.Groq") def test_get_intent_classifier_returns_same_instance(self, mock_groq): classifier1 = get_intent_classifier() classifier2 = get_intent_classifier() assert classifier1 is classifier2 @patch("src.agent.intent_classifier.Groq") def test_reset_intent_classifier(self, mock_groq): classifier1 = get_intent_classifier() reset_intent_classifier() classifier2 = get_intent_classifier() assert classifier1 is not classifier2 class TestIntentClassifierEdgeCases: # Tests for edge cases and confidence thresholds @pytest.fixture def classifier(self): with patch("src.agent.intent_classifier.Groq") as mock_groq_class: mock_client = Mock() mock_groq_class.return_value = mock_client classifier = IntentClassifier(api_key="test_key", confidence_threshold=0.7) classifier._client = mock_client return classifier def test_classify_empty_query(self, classifier): # Empty query should return AMBIGUOUS with requires_clarification result = classifier.classify("") assert result.intent == IntentType.AMBIGUOUS assert result.requires_clarification is True assert result.confidence == 1.0 def test_classify_whitespace_only_query(self, classifier): result = classifier.classify(" ") assert result.intent == IntentType.AMBIGUOUS assert result.requires_clarification is True def test_classify_with_available_tables(self, classifier): # Verify available_tables is included in prompt mock_response = Mock() mock_response.choices = [ Mock(message=Mock(content='{"intent": "DATABASE_QUERY", "confidence": 0.9, "reasoning": "Tables hint", "suggested_tables": ["CreditFileVersion"]}')) ] classifier._client.chat.completions.create.return_value = mock_response result = classifier.classify( "Kac kredi dosyasi var?", available_tables=["CreditFileVersion", "CreditFileElement", "Contract"], ) assert result.intent == IntentType.DATABASE_QUERY # Verify the LLM was called with tables context call_args = classifier._client.chat.completions.create.call_args user_msg = call_args[1]["messages"][1]["content"] assert "CreditFileVersion" in user_msg def test_low_confidence_sets_requires_clarification(self, classifier): mock_response = Mock() mock_response.choices = [ Mock(message=Mock(content='{"intent": "DATABASE_QUERY", "confidence": 0.4, "reasoning": "Not sure"}')) ] classifier._client.chat.completions.create.return_value = mock_response result = classifier.classify("bilgi ver") assert result.confidence == 0.4 assert result.requires_clarification is True def test_high_confidence_no_clarification(self, classifier): mock_response = Mock() mock_response.choices = [ Mock(message=Mock(content='{"intent": "DOCUMENT_QUERY", "confidence": 0.95, "reasoning": "Clear question"}')) ] classifier._client.chat.completions.create.return_value = mock_response result = classifier.classify("Atlas ERP nedir?") assert result.confidence == 0.95 assert result.requires_clarification is False def test_classify_json_with_code_fence(self, classifier): # Test that JSON wrapped in code fences is parsed correctly mock_response = Mock() mock_response.choices = [ Mock(message=Mock(content='```json\n{"intent": "CONVERSATIONAL", "confidence": 0.9, "reasoning": "Greeting"}\n```')) ] classifier._client.chat.completions.create.return_value = mock_response result = classifier.classify("Merhaba!") assert result.intent == IntentType.CONVERSATIONAL def test_classify_batch(self, classifier): mock_response = Mock() mock_response.choices = [ Mock(message=Mock(content='{"intent": "DOCUMENT_QUERY", "confidence": 0.8, "reasoning": "test"}')) ] classifier._client.chat.completions.create.return_value = mock_response results = classifier.classify_batch(["q1", "q2", "q3"]) assert len(results) == 3 assert all(isinstance(r, ClassificationResult) for r in results) def test_classification_time_recorded(self, classifier): mock_response = Mock() mock_response.choices = [ Mock(message=Mock(content='{"intent": "DOCUMENT_QUERY", "confidence": 0.8, "reasoning": "test"}')) ] classifier._client.chat.completions.create.return_value = mock_response result = classifier.classify("test query") assert result.classification_time_ms >= 0