Files
wehub-resource-sync 7a0da7932b
OSV-Scanner (Scheduled) / scan-scheduled (push) Failing after 0s
Create Release / test-gate (push) Has been cancelled
Create Release / release-gate (push) Has been cancelled
Create Release / ci-gate (push) Has been cancelled
Create Release / version-check (push) Has been cancelled
Create Release / e2e-test-gate (push) Has been cancelled
Create Release / responsive-test-gate (push) Has been cancelled
Create Release / compat-test-gate (push) Has been cancelled
Create Release / compose-integration-gate (push) Has been cancelled
Create Release / vulture-gate (push) Has been cancelled
Create Release / build (push) Has been cancelled
Create Release / provenance (push) Has been cancelled
Create Release / prerelease-docker (push) Has been cancelled
Create Release / publish-docker (push) Has been cancelled
Create Release / create-release (push) Has been cancelled
Create Release / cleanup-changelog (push) Has been cancelled
Create Release / trigger-pypi (push) Has been cancelled
Create Release / monitor-pypi (push) Has been cancelled
Create Release / Clean up orphan prerelease tags and signatures (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-form] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-metrics] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-workflow] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [settings-core] (push) Has been cancelled
CodeQL Advanced / Analyze (javascript-typescript) (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [history-news] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [library] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [link-analytics] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [chat-core] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [chat-lifecycle] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [error-benchmark] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [settings-pages] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) (push) Has been cancelled
Docker Tests (Consolidated) / Accessibility Tests (push) Has been cancelled
Docker Tests (Consolidated) / LLM Unit Tests (push) Has been cancelled
Docker Tests (Consolidated) / LLM Example Tests (push) Has been cancelled
Docker Tests (Consolidated) / Production Image Smoke Test (push) Has been cancelled
Docker Tests (Consolidated) / Infrastructure Tests (push) Has been cancelled
OSSF Scorecard / OSSF Security Scorecard Analysis (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [mobile] (push) Has been cancelled
Backwards Compatibility / Verify Encryption Constants (push) Has been cancelled
Backwards Compatibility / PyPI Version Compatibility (push) Has been cancelled
Backwards Compatibility / Database Migration Tests (push) Has been cancelled
CodeQL Advanced / Analyze (python) (push) Has been cancelled
Docker Tests (Consolidated) / detect-changes (push) Has been cancelled
Docker Tests (Consolidated) / Build Test Image (push) Has been cancelled
Docker Tests (Consolidated) / All Pytest Tests + Coverage (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [accessibility] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [api-crud] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-login] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-pages] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-register] (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:08:55 +08:00

368 lines
12 KiB
Python

"""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