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

352 lines
13 KiB
Python

"""Tests for error_reporter module."""
from unittest.mock import MagicMock, patch
from local_deep_research.error_handling.error_reporter import (
ErrorCategory,
)
class TestErrorCategory:
"""Tests for ErrorCategory enum."""
def test_has_all_expected_categories(self):
"""Should have all expected error categories."""
expected = [
"CONNECTION_ERROR",
"MODEL_ERROR",
"SEARCH_ERROR",
"SYNTHESIS_ERROR",
"FILE_ERROR",
"RATE_LIMIT_ERROR",
"UNKNOWN_ERROR",
]
for category in expected:
assert hasattr(ErrorCategory, category)
def test_values_are_strings(self):
"""Should have string values."""
for category in ErrorCategory:
assert isinstance(category.value, str)
class TestErrorReporterInit:
"""Tests for ErrorReporter initialization."""
def test_initializes_error_patterns(self, error_reporter):
"""Should initialize with error patterns."""
assert hasattr(error_reporter, "error_patterns")
assert isinstance(error_reporter.error_patterns, dict)
def test_has_patterns_for_all_categories(self, error_reporter):
"""Should have patterns for all categories except UNKNOWN."""
for category in ErrorCategory:
if category != ErrorCategory.UNKNOWN_ERROR:
assert category in error_reporter.error_patterns
class TestCategorizeError:
"""Tests for categorize_error method."""
def test_categorizes_connection_error(self, error_reporter):
"""Should categorize connection errors."""
error_messages = [
"POST predict EOF",
"Connection refused",
"timeout waiting for response",
"[Errno 111] Connection refused",
]
for msg in error_messages:
result = error_reporter.categorize_error(msg)
assert result == ErrorCategory.CONNECTION_ERROR
def test_categorizes_model_error(self, error_reporter):
"""Should categorize model errors."""
error_messages = [
"Model not found in Ollama",
"Invalid model specified",
"API key invalid",
"401 API key error",
]
for msg in error_messages:
result = error_reporter.categorize_error(msg)
assert result == ErrorCategory.MODEL_ERROR
def test_categorizes_rate_limit_error(self, error_reporter):
"""Should categorize rate limit errors."""
error_messages = [
"429 resource exhausted",
"rate limit exceeded",
"quota exceeded",
"LLM rate limit reached",
]
for msg in error_messages:
result = error_reporter.categorize_error(msg)
assert result == ErrorCategory.RATE_LIMIT_ERROR
def test_categorizes_search_error(self, error_reporter):
"""Should categorize search errors."""
error_messages = [
"Search failed",
"No search results found",
"The search is longer than 256 characters",
"GitHub API error occurred",
]
for msg in error_messages:
result = error_reporter.categorize_error(msg)
assert result == ErrorCategory.SEARCH_ERROR
def test_categorizes_synthesis_error(self, error_reporter):
"""Should categorize synthesis errors."""
error_messages = [
"Error during synthesis process",
"Failed to generate the report",
"detailed report stuck at 99%",
"report taking too long to complete",
]
for msg in error_messages:
result = error_reporter.categorize_error(msg)
assert result == ErrorCategory.SYNTHESIS_ERROR, (
f"'{msg}' should be SYNTHESIS_ERROR"
)
def test_categorizes_file_error(self, error_reporter):
"""Should categorize file errors."""
error_messages = [
"Permission denied",
"Cannot write to file",
"Disk is full",
"No module named local_deep_research",
]
for msg in error_messages:
result = error_reporter.categorize_error(msg)
assert result == ErrorCategory.FILE_ERROR, (
f"'{msg}' should be FILE_ERROR"
)
def test_returns_unknown_for_unmatched(self, error_reporter):
"""Should return UNKNOWN_ERROR for unmatched errors."""
result = error_reporter.categorize_error("Some random error message")
assert result == ErrorCategory.UNKNOWN_ERROR
def test_case_insensitive_matching(self, error_reporter):
"""Should match patterns case-insensitively."""
result = error_reporter.categorize_error("CONNECTION REFUSED")
assert result == ErrorCategory.CONNECTION_ERROR
class TestGetUserFriendlyTitle:
"""Tests for get_user_friendly_title method."""
def test_returns_title_for_each_category(self, error_reporter):
"""Should return a title for each category."""
for category in ErrorCategory:
result = error_reporter.get_user_friendly_title(category)
assert isinstance(result, str)
assert len(result) > 0
def test_returns_specific_titles(self, error_reporter):
"""Should return specific expected titles."""
assert (
error_reporter.get_user_friendly_title(
ErrorCategory.CONNECTION_ERROR
)
== "Connection Issue"
)
assert (
error_reporter.get_user_friendly_title(ErrorCategory.MODEL_ERROR)
== "LLM Service Error"
)
assert (
error_reporter.get_user_friendly_title(
ErrorCategory.RATE_LIMIT_ERROR
)
== "API Rate Limit Exceeded"
)
class TestGetSuggestedActions:
"""Tests for get_suggested_actions method."""
def test_returns_list_for_each_category(self, error_reporter):
"""Should return a list for each category."""
for category in ErrorCategory:
result = error_reporter.get_suggested_actions(category)
assert isinstance(result, list)
assert len(result) > 0
def test_suggestions_are_strings(self, error_reporter):
"""Should return list of strings."""
for category in ErrorCategory:
suggestions = error_reporter.get_suggested_actions(category)
for suggestion in suggestions:
assert isinstance(suggestion, str)
class TestAnalyzeError:
"""Tests for analyze_error method."""
def test_returns_dict_with_expected_keys(self, error_reporter):
"""Should return dict with expected keys."""
result = error_reporter.analyze_error("Connection refused")
expected_keys = [
"category",
"title",
"original_error",
"suggestions",
"severity",
"recoverable",
]
for key in expected_keys:
assert key in result
def test_includes_context_when_provided(self, error_reporter):
"""Should include context when provided."""
context = {"query": "test query", "findings": ["finding1"]}
result = error_reporter.analyze_error("Connection refused", context)
assert "context" in result
assert result["context"] == context
def test_detects_partial_results(
self, error_reporter, sample_partial_results
):
"""Should detect partial results in context."""
result = error_reporter.analyze_error(
"Error occurred", sample_partial_results
)
assert result["has_partial_results"] is True
def test_no_partial_results_when_empty(self, error_reporter):
"""Should not have partial results when context is empty."""
result = error_reporter.analyze_error("Error occurred", {})
assert result.get("has_partial_results", False) is False
class TestExtractServiceName:
"""Tests for _extract_service_name method."""
def test_extracts_openai(self, error_reporter):
"""Should extract OpenAI service name."""
result = error_reporter._extract_service_name(
"OpenAI API returned error"
)
assert result == "Openai"
def test_extracts_anthropic(self, error_reporter):
"""Should extract Anthropic service name."""
result = error_reporter._extract_service_name("Anthropic rate limit")
assert result == "Anthropic"
def test_extracts_ollama(self, error_reporter):
"""Should extract Ollama service name."""
result = error_reporter._extract_service_name(
"Ollama connection failed"
)
assert result == "Ollama"
def test_returns_default_for_unknown(self, error_reporter):
"""Should return default for unknown service."""
result = error_reporter._extract_service_name("Unknown error")
assert result == "API Service"
class TestDetermineSeverity:
"""Tests for _determine_severity method."""
def test_connection_error_is_high(self, error_reporter):
"""Should return high for connection errors."""
result = error_reporter._determine_severity(
ErrorCategory.CONNECTION_ERROR
)
assert result == "high"
def test_model_error_is_high(self, error_reporter):
"""Should return high for model errors."""
result = error_reporter._determine_severity(ErrorCategory.MODEL_ERROR)
assert result == "high"
def test_synthesis_error_is_low(self, error_reporter):
"""Should return low for synthesis errors."""
result = error_reporter._determine_severity(
ErrorCategory.SYNTHESIS_ERROR
)
assert result == "low"
def test_rate_limit_is_medium(self, error_reporter):
"""Should return medium for rate limit errors."""
result = error_reporter._determine_severity(
ErrorCategory.RATE_LIMIT_ERROR
)
assert result == "medium"
class TestIsRecoverable:
"""Tests for _is_recoverable method."""
def test_unknown_error_not_recoverable(self, error_reporter):
"""Should return False for unknown errors."""
result = error_reporter._is_recoverable(ErrorCategory.UNKNOWN_ERROR)
assert result is False
def test_connection_error_recoverable(self, error_reporter):
"""Should return True for connection errors."""
result = error_reporter._is_recoverable(ErrorCategory.CONNECTION_ERROR)
assert result is True
def test_rate_limit_recoverable(self, error_reporter):
"""Should return True for rate limit errors."""
result = error_reporter._is_recoverable(ErrorCategory.RATE_LIMIT_ERROR)
assert result is True
class TestSendErrorNotifications:
"""Tests for _send_error_notifications method."""
def test_skips_when_no_username(self, error_reporter):
"""Should skip notification when no username in context."""
# Should not raise, just skip
error_reporter._send_error_notifications(
ErrorCategory.MODEL_ERROR, "API key invalid", {}
)
def test_skips_non_notifiable_categories(self, error_reporter):
"""Should skip notification for non-notifiable categories."""
context = {"username": "testuser"}
# Should not raise, just skip
error_reporter._send_error_notifications(
ErrorCategory.SEARCH_ERROR, "Search failed", context
)
def test_sends_auth_notification_for_auth_errors(
self, error_reporter, mock_context_with_username
):
"""Should send auth notification for auth errors."""
# Patch at source locations since these are imported inside the method
with patch(
"local_deep_research.database.session_context.get_user_db_session"
) as mock_session:
mock_cm = MagicMock()
mock_cm.__enter__ = MagicMock(return_value=MagicMock())
mock_cm.__exit__ = MagicMock(return_value=None)
mock_session.return_value = mock_cm
with patch(
"local_deep_research.settings.SettingsManager"
) as mock_settings:
mock_settings.return_value.get_settings_snapshot.return_value = {}
with patch(
"local_deep_research.notifications.manager.NotificationManager"
) as mock_nm:
mock_nm_instance = MagicMock()
mock_nm.return_value = mock_nm_instance
error_reporter._send_error_notifications(
ErrorCategory.MODEL_ERROR,
"401 API key error",
mock_context_with_username,
)
mock_nm_instance.send_notification.assert_called_once()