"""Tests for domain classifier.""" from unittest.mock import MagicMock, patch from local_deep_research.domain_classifier.classifier import ( DOMAIN_CATEGORIES, DomainClassifier, ) class TestDomainCategories: """Tests for DOMAIN_CATEGORIES constant.""" def test_has_expected_main_categories(self): """Should have all expected main categories.""" expected = [ "Academic & Research", "News & Media", "Reference & Documentation", "Social & Community", "Business & Commerce", "Technology", "Government & Organization", "Entertainment & Lifestyle", "Professional & Industry", "Other", ] for category in expected: assert category in DOMAIN_CATEGORIES def test_each_category_has_subcategories(self): """Should have subcategories for each main category.""" for category, subcategories in DOMAIN_CATEGORIES.items(): assert isinstance(subcategories, list) assert len(subcategories) > 0 def test_other_includes_unknown(self): """Other category should include Unknown subcategory.""" assert "Unknown" in DOMAIN_CATEGORIES["Other"] class TestDomainClassifierInit: """Tests for DomainClassifier initialization.""" def test_stores_username(self): """Should store username.""" classifier = DomainClassifier(username="testuser") assert classifier.username == "testuser" def test_stores_settings_snapshot(self): """Should store settings snapshot.""" snapshot = {"key": "value"} classifier = DomainClassifier( username="testuser", settings_snapshot=snapshot ) assert classifier.settings_snapshot == snapshot def test_llm_initially_none(self): """Should have LLM as None initially.""" classifier = DomainClassifier(username="testuser") assert classifier.llm is None class TestGetLlm: """Tests for _get_llm method.""" def test_creates_llm_on_first_call(self): """Should create LLM on first call.""" mock_llm = MagicMock() classifier = DomainClassifier(username="testuser") with patch( "local_deep_research.domain_classifier.classifier.get_llm", return_value=mock_llm, ): result = classifier._get_llm() assert result is mock_llm assert classifier.llm is mock_llm def test_reuses_existing_llm(self): """Should reuse existing LLM on subsequent calls.""" mock_llm = MagicMock() classifier = DomainClassifier(username="testuser") classifier.llm = mock_llm with patch( "local_deep_research.domain_classifier.classifier.get_llm" ) as mock_get: result = classifier._get_llm() mock_get.assert_not_called() assert result is mock_llm def test_passes_settings_snapshot(self): """Should pass settings snapshot to get_llm.""" snapshot = {"key": "value"} classifier = DomainClassifier( username="testuser", settings_snapshot=snapshot ) with patch( "local_deep_research.domain_classifier.classifier.get_llm" ) as mock_get: classifier._get_llm() mock_get.assert_called_once_with(settings_snapshot=snapshot) class TestBuildClassificationPrompt: """Tests for _build_classification_prompt method.""" def test_includes_domain(self): """Should include domain in prompt.""" classifier = DomainClassifier(username="testuser") samples = [] prompt = classifier._build_classification_prompt("example.com", samples) assert "example.com" in prompt def test_includes_categories(self, sample_samples): """Should include categories in prompt.""" classifier = DomainClassifier(username="testuser") prompt = classifier._build_classification_prompt( "example.com", sample_samples ) assert "News & Media" in prompt assert "Technology" in prompt def test_includes_samples(self, sample_samples): """Should include samples in prompt.""" classifier = DomainClassifier(username="testuser") prompt = classifier._build_classification_prompt( "example.com", sample_samples ) assert "Article 1" in prompt assert "Article 2" in prompt def test_handles_empty_samples(self): """Should handle empty samples.""" classifier = DomainClassifier(username="testuser") prompt = classifier._build_classification_prompt("example.com", []) assert "No samples available" in prompt def test_requests_json_response(self, sample_samples): """Should request JSON response format.""" classifier = DomainClassifier(username="testuser") prompt = classifier._build_classification_prompt( "example.com", sample_samples ) assert "JSON" in prompt assert "category" in prompt assert "subcategory" in prompt assert "confidence" in prompt class TestClassifyDomain: """Tests for classify_domain method.""" def test_returns_existing_classification(self, mock_domain_classification): """Should return existing classification without reclassifying.""" classifier = DomainClassifier(username="testuser") with patch( "local_deep_research.domain_classifier.classifier.get_user_db_session" ) as mock_session_ctx: mock_session = MagicMock() mock_session_ctx.return_value.__enter__ = MagicMock( return_value=mock_session ) mock_session_ctx.return_value.__exit__ = MagicMock( return_value=None ) mock_session.query.return_value.filter_by.return_value.first.return_value = mock_domain_classification result = classifier.classify_domain("example.com") assert result is mock_domain_classification def test_returns_none_on_error(self): """Should return None on error.""" classifier = DomainClassifier(username="testuser") with patch( "local_deep_research.domain_classifier.classifier.get_user_db_session" ) as mock_session_ctx: mock_session_ctx.return_value.__enter__ = MagicMock( side_effect=Exception("DB error") ) result = classifier.classify_domain("example.com") assert result is None class TestGetClassification: """Tests for get_classification method.""" def test_returns_classification_when_exists( self, mock_domain_classification ): """Should return classification when exists.""" classifier = DomainClassifier(username="testuser") with patch( "local_deep_research.domain_classifier.classifier.get_user_db_session" ) as mock_session_ctx: mock_session = MagicMock() mock_session_ctx.return_value.__enter__ = MagicMock( return_value=mock_session ) mock_session_ctx.return_value.__exit__ = MagicMock( return_value=None ) mock_session.query.return_value.filter_by.return_value.first.return_value = mock_domain_classification result = classifier.get_classification("example.com") assert result is mock_domain_classification def test_returns_none_when_not_found(self): """Should return None when not found.""" classifier = DomainClassifier(username="testuser") with patch( "local_deep_research.domain_classifier.classifier.get_user_db_session" ) as mock_session_ctx: mock_session = MagicMock() mock_session_ctx.return_value.__enter__ = MagicMock( return_value=mock_session ) mock_session_ctx.return_value.__exit__ = MagicMock( return_value=None ) mock_session.query.return_value.filter_by.return_value.first.return_value = None result = classifier.get_classification("unknown.com") assert result is None def test_returns_none_on_error(self): """Should return None on error.""" classifier = DomainClassifier(username="testuser") with patch( "local_deep_research.domain_classifier.classifier.get_user_db_session" ) as mock_session_ctx: mock_session_ctx.return_value.__enter__ = MagicMock( side_effect=Exception("DB error") ) result = classifier.get_classification("example.com") assert result is None class TestGetAllClassifications: """Tests for get_all_classifications method.""" def test_returns_list_of_classifications(self, mock_domain_classification): """Should return list of classifications.""" classifier = DomainClassifier(username="testuser") with patch( "local_deep_research.domain_classifier.classifier.get_user_db_session" ) as mock_session_ctx: mock_session = MagicMock() mock_session_ctx.return_value.__enter__ = MagicMock( return_value=mock_session ) mock_session_ctx.return_value.__exit__ = MagicMock( return_value=None ) mock_session.query.return_value.order_by.return_value.all.return_value = [ mock_domain_classification ] result = classifier.get_all_classifications() assert len(result) == 1 assert result[0] is mock_domain_classification def test_returns_empty_list_on_error(self): """Should return empty list on error.""" classifier = DomainClassifier(username="testuser") with patch( "local_deep_research.domain_classifier.classifier.get_user_db_session" ) as mock_session_ctx: mock_session_ctx.return_value.__enter__ = MagicMock( side_effect=Exception("DB error") ) result = classifier.get_all_classifications() assert result == [] class TestGetCategoriesSummary: """Tests for get_categories_summary method.""" def test_returns_summary_dict(self, mock_domain_classification): """Should return summary dictionary.""" classifier = DomainClassifier(username="testuser") with patch( "local_deep_research.domain_classifier.classifier.get_user_db_session" ) as mock_session_ctx: mock_session = MagicMock() mock_session_ctx.return_value.__enter__ = MagicMock( return_value=mock_session ) mock_session_ctx.return_value.__exit__ = MagicMock( return_value=None ) mock_session.query.return_value.all.return_value = [ mock_domain_classification ] result = classifier.get_categories_summary() assert "News & Media" in result assert result["News & Media"]["count"] == 1 def test_returns_empty_dict_on_error(self): """Should return empty dict on error.""" classifier = DomainClassifier(username="testuser") with patch( "local_deep_research.domain_classifier.classifier.get_user_db_session" ) as mock_session_ctx: mock_session_ctx.return_value.__enter__ = MagicMock( side_effect=Exception("DB error") ) result = classifier.get_categories_summary() assert result == {} class TestUrlparseValueErrorHandling: """Tests for ValueError handling in urlparse (PR #2012). PR #2012 changed bare `except:` to `except ValueError:` when parsing URLs in classify_all_domains. """ def test_value_error_from_urlparse_caught(self): """ValueError from urlparse is caught with continue.""" from urllib.parse import urlparse # URLs that could cause issues test_urls = ["https://valid.com/path", "", "not-a-url"] domains = set() for url in test_urls: if url: try: parsed = urlparse(url) domain = parsed.netloc.lower() if domain.startswith("www."): domain = domain[4:] if domain: domains.add(domain) except ValueError: continue assert "valid.com" in domains