Files
wehub-resource-sync 7a0da7932b
Backwards Compatibility / Verify Encryption Constants (push) Waiting to run
Backwards Compatibility / PyPI Version Compatibility (push) Waiting to run
Backwards Compatibility / Database Migration Tests (push) Waiting to run
CodeQL Advanced / Analyze (javascript-typescript) (push) Waiting to run
CodeQL Advanced / Analyze (python) (push) Waiting to run
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-form] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-metrics] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-workflow] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [settings-core] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [settings-pages] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [history-news] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [library] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [link-analytics] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [mobile] (push) Blocked by required conditions
Docker Tests (Consolidated) / detect-changes (push) Waiting to run
Docker Tests (Consolidated) / Build Test Image (push) Waiting to run
Docker Tests (Consolidated) / All Pytest Tests + Coverage (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [accessibility] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [api-crud] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-login] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-pages] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-register] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [chat-core] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [chat-lifecycle] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [error-benchmark] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) (push) Blocked by required conditions
Docker Tests (Consolidated) / Accessibility Tests (push) Blocked by required conditions
Docker Tests (Consolidated) / LLM Unit Tests (push) Blocked by required conditions
Docker Tests (Consolidated) / LLM Example Tests (push) Blocked by required conditions
Docker Tests (Consolidated) / Production Image Smoke Test (push) Blocked by required conditions
Docker Tests (Consolidated) / Infrastructure Tests (push) Blocked by required conditions
OSSF Scorecard / OSSF Security Scorecard Analysis (push) Waiting to run
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
chore: import upstream snapshot with attribution
2026-07-13 13:08:55 +08:00

514 lines
17 KiB
Python

"""
Tests for error_handling/error_reporter.py - Error Categorization
Tests cover:
- Error message pattern matching
- Category assignment for different error types
- Edge cases in pattern matching
- User-friendly error information
These tests ensure users get helpful error messages and guidance.
"""
import pytest
class TestErrorCategorization:
"""Tests for error categorization logic."""
@pytest.fixture
def reporter(self):
"""Create an ErrorReporter instance."""
from local_deep_research.error_handling.error_reporter import (
ErrorReporter,
)
return ErrorReporter()
def test_connection_error_detected(self, reporter):
"""'Connection refused' -> CONNECTION_ERROR."""
from local_deep_research.error_handling.error_reporter import (
ErrorCategory,
)
# Test various connection error patterns
test_cases = [
"Connection refused",
"POST predict EOF error",
"Connection failed",
"timeout waiting for response",
"HTTP error 500",
"network error occurred",
"[Errno 111] Connection refused",
"host.docker.internal not reachable",
]
for error_msg in test_cases:
category = reporter.categorize_error(error_msg)
assert category == ErrorCategory.CONNECTION_ERROR, (
f"Failed for: {error_msg}"
)
def test_model_error_detected(self, reporter):
"""'Model not found' -> MODEL_ERROR."""
from local_deep_research.error_handling.error_reporter import (
ErrorCategory,
)
test_cases = [
"Model xyz not found",
"Invalid model specified",
"Ollama is not available",
"API key is invalid",
"Authentication error",
"max_workers must be greater than 0",
"TypeError Context Size",
"No auth credentials found",
"401 - API key",
]
for error_msg in test_cases:
category = reporter.categorize_error(error_msg)
assert category == ErrorCategory.MODEL_ERROR, (
f"Failed for: {error_msg}"
)
def test_rate_limit_error_detected(self, reporter):
"""'rate limit' -> RATE_LIMIT_ERROR."""
from local_deep_research.error_handling.error_reporter import (
ErrorCategory,
)
test_cases = [
"429 resource exhausted",
"429 too many requests",
"rate limit exceeded",
"rate_limit hit",
"ratelimit reached",
"quota exceeded",
"resource exhausted - quota",
"LLM rate limit reached",
"API rate limit",
"maximum requests per minute",
]
for error_msg in test_cases:
category = reporter.categorize_error(error_msg)
assert category == ErrorCategory.RATE_LIMIT_ERROR, (
f"Failed for: {error_msg}"
)
def test_timeout_error_detected(self, reporter):
"""'timeout' -> CONNECTION_ERROR (timeout is connection-related)."""
from local_deep_research.error_handling.error_reporter import (
ErrorCategory,
)
# Note: timeout is in CONNECTION_ERROR patterns
# The pattern matches "timeout" exactly (case-insensitive)
test_cases = [
"timeout",
"Connection timeout",
"The request timeout occurred", # Contains "timeout"
]
for error_msg in test_cases:
category = reporter.categorize_error(error_msg)
# Timeout is categorized as CONNECTION_ERROR
assert category == ErrorCategory.CONNECTION_ERROR, (
f"Failed for: {error_msg}"
)
def test_overlapping_patterns_priority(self, reporter):
"""First matching pattern wins."""
from local_deep_research.error_handling.error_reporter import (
ErrorCategory,
)
# Test message that could match multiple patterns
# "Connection timeout" has both "Connection" and "timeout"
category = reporter.categorize_error("Connection timeout")
# Should match CONNECTION_ERROR first
assert category == ErrorCategory.CONNECTION_ERROR
def test_partial_match_rejected(self, reporter):
"""'settimeout' doesn't match 'timeout' pattern."""
from local_deep_research.error_handling.error_reporter import (
ErrorCategory,
)
# The regex pattern is just "timeout", which will match "settimeout"
# This tests the actual behavior
category = reporter.categorize_error("settimeout error occurred")
# Note: This WILL match because regex doesn't have word boundaries
# The pattern "timeout" is contained in "settimeout"
# This test documents the current behavior
assert category == ErrorCategory.CONNECTION_ERROR
def test_case_insensitive_matching(self, reporter):
"""'TIMEOUT' matches timeout pattern."""
from local_deep_research.error_handling.error_reporter import (
ErrorCategory,
)
test_cases = [
("TIMEOUT", ErrorCategory.CONNECTION_ERROR),
("RATE LIMIT", ErrorCategory.RATE_LIMIT_ERROR),
("MODEL NOT FOUND", ErrorCategory.MODEL_ERROR),
("CONNECTION REFUSED", ErrorCategory.CONNECTION_ERROR),
]
for error_msg, expected in test_cases:
category = reporter.categorize_error(error_msg)
assert category == expected, f"Failed for: {error_msg}"
def test_multiline_error_message(self, reporter):
"""Multi-line errors parsed correctly."""
from local_deep_research.error_handling.error_reporter import (
ErrorCategory,
)
multiline_error = """
Error occurred:
Connection refused
at line 123
in file xyz.py
"""
category = reporter.categorize_error(multiline_error)
assert category == ErrorCategory.CONNECTION_ERROR
def test_empty_error_returns_unknown(self, reporter):
"""Empty string -> UNKNOWN_ERROR."""
from local_deep_research.error_handling.error_reporter import (
ErrorCategory,
)
category = reporter.categorize_error("")
assert category == ErrorCategory.UNKNOWN_ERROR
category = reporter.categorize_error(" ")
assert category == ErrorCategory.UNKNOWN_ERROR
def test_very_long_error_performance(self, reporter):
"""10KB error message doesn't hang."""
from local_deep_research.error_handling.error_reporter import (
ErrorCategory,
)
# Create a very long error message (10KB)
long_error = "x" * 10000 + " Connection refused " + "y" * 10000
import time
start = time.time()
category = reporter.categorize_error(long_error)
elapsed = time.time() - start
# Should complete within reasonable time (< 1 second)
assert elapsed < 1.0
assert category == ErrorCategory.CONNECTION_ERROR
class TestUserFriendlyTitles:
"""Tests for user-friendly error titles."""
@pytest.fixture
def reporter(self):
"""Create an ErrorReporter instance."""
from local_deep_research.error_handling.error_reporter import (
ErrorReporter,
)
return ErrorReporter()
def test_all_categories_have_titles(self, reporter):
"""All error categories have user-friendly titles."""
from local_deep_research.error_handling.error_reporter import (
ErrorCategory,
)
for category in ErrorCategory:
title = reporter.get_user_friendly_title(category)
assert title is not None
assert len(title) > 0
def test_title_content(self, reporter):
"""Titles are meaningful."""
from local_deep_research.error_handling.error_reporter import (
ErrorCategory,
)
expected_titles = {
ErrorCategory.CONNECTION_ERROR: "Connection Issue",
ErrorCategory.MODEL_ERROR: "LLM Service Error",
ErrorCategory.SEARCH_ERROR: "Search Service Error",
ErrorCategory.RATE_LIMIT_ERROR: "API Rate Limit Exceeded",
ErrorCategory.UNKNOWN_ERROR: "Unexpected Error",
}
for category, expected in expected_titles.items():
actual = reporter.get_user_friendly_title(category)
assert actual == expected
class TestSuggestedActions:
"""Tests for suggested action lists."""
@pytest.fixture
def reporter(self):
"""Create an ErrorReporter instance."""
from local_deep_research.error_handling.error_reporter import (
ErrorReporter,
)
return ErrorReporter()
def test_all_categories_have_suggestions(self, reporter):
"""All categories have suggested actions."""
from local_deep_research.error_handling.error_reporter import (
ErrorCategory,
)
for category in ErrorCategory:
suggestions = reporter.get_suggested_actions(category)
assert suggestions is not None
assert isinstance(suggestions, list)
assert len(suggestions) > 0
def test_suggestions_are_actionable(self, reporter):
"""Suggestions contain actionable text."""
from local_deep_research.error_handling.error_reporter import (
ErrorCategory,
)
suggestions = reporter.get_suggested_actions(
ErrorCategory.CONNECTION_ERROR
)
# Should have multiple suggestions
assert len(suggestions) >= 2
# Each should be a non-empty string
for suggestion in suggestions:
assert isinstance(suggestion, str)
assert len(suggestion) > 10 # Meaningful text
class TestErrorAnalysis:
"""Tests for comprehensive error analysis."""
@pytest.fixture
def reporter(self):
"""Create an ErrorReporter instance."""
from local_deep_research.error_handling.error_reporter import (
ErrorReporter,
)
return ErrorReporter()
def test_analyze_error_returns_complete_structure(self, reporter):
"""analyze_error returns all expected keys."""
analysis = reporter.analyze_error("Connection refused")
assert "category" in analysis
assert "title" in analysis
assert "original_error" in analysis
assert "suggestions" in analysis
assert "severity" in analysis
assert "recoverable" in analysis
def test_analyze_error_with_context(self, reporter):
"""Context information is included."""
context = {
"findings": [{"content": "some data"}],
"current_knowledge": "existing info",
}
analysis = reporter.analyze_error("Connection refused", context=context)
assert "context" in analysis
assert "has_partial_results" in analysis
assert analysis["has_partial_results"] is True
def test_severity_levels(self, reporter):
"""Severity levels are appropriate."""
from local_deep_research.error_handling.error_reporter import (
ErrorCategory,
)
severity_expectations = {
ErrorCategory.CONNECTION_ERROR: "high",
ErrorCategory.MODEL_ERROR: "high",
ErrorCategory.SEARCH_ERROR: "medium",
ErrorCategory.SYNTHESIS_ERROR: "low",
ErrorCategory.FILE_ERROR: "medium",
ErrorCategory.RATE_LIMIT_ERROR: "medium",
ErrorCategory.UNKNOWN_ERROR: "high",
}
for category, expected_severity in severity_expectations.items():
actual = reporter._determine_severity(category)
assert actual == expected_severity, f"Failed for {category}"
def test_recoverability(self, reporter):
"""Recoverability is correctly determined."""
from local_deep_research.error_handling.error_reporter import (
ErrorCategory,
)
# Most errors should be recoverable
recoverable_categories = [
ErrorCategory.CONNECTION_ERROR,
ErrorCategory.MODEL_ERROR,
ErrorCategory.SEARCH_ERROR,
ErrorCategory.SYNTHESIS_ERROR,
ErrorCategory.FILE_ERROR,
ErrorCategory.RATE_LIMIT_ERROR,
]
for category in recoverable_categories:
assert reporter._is_recoverable(category) is True
# Unknown errors are not recoverable
assert reporter._is_recoverable(ErrorCategory.UNKNOWN_ERROR) is False
class TestSearchErrorPatterns:
"""Tests for search-related error patterns."""
@pytest.fixture
def reporter(self):
"""Create an ErrorReporter instance."""
from local_deep_research.error_handling.error_reporter import (
ErrorReporter,
)
return ErrorReporter()
def test_search_error_patterns(self, reporter):
"""Search error patterns are detected."""
from local_deep_research.error_handling.error_reporter import (
ErrorCategory,
)
test_cases = [
"Search failed",
"No search results found",
"Search engine error",
"The search is longer than 256 characters",
"Failed to create search engine",
"search engine 'brave' could not be found",
"GitHub API error",
]
for error_msg in test_cases:
category = reporter.categorize_error(error_msg)
assert category == ErrorCategory.SEARCH_ERROR, (
f"Failed for: {error_msg}"
)
class TestSynthesisErrorPatterns:
"""Tests for synthesis-related error patterns."""
@pytest.fixture
def reporter(self):
"""Create an ErrorReporter instance."""
from local_deep_research.error_handling.error_reporter import (
ErrorReporter,
)
return ErrorReporter()
def test_synthesis_error_patterns(self, reporter):
"""Synthesis error patterns are detected."""
from local_deep_research.error_handling.error_reporter import (
ErrorCategory,
)
# Note: "Synthesis timeout" would match CONNECTION_ERROR due to "timeout"
# Pattern matching is priority-based
test_cases = [
"Error during synthesis",
"Failed to generate report",
"detailed report stuck",
"report taking too long",
"progress at 100 stuck",
]
for error_msg in test_cases:
category = reporter.categorize_error(error_msg)
assert category == ErrorCategory.SYNTHESIS_ERROR, (
f"Failed for: {error_msg}"
)
class TestFileErrorPatterns:
"""Tests for file-related error patterns."""
@pytest.fixture
def reporter(self):
"""Create an ErrorReporter instance."""
from local_deep_research.error_handling.error_reporter import (
ErrorReporter,
)
return ErrorReporter()
def test_file_error_patterns(self, reporter):
"""File error patterns are detected."""
from local_deep_research.error_handling.error_reporter import (
ErrorCategory,
)
# Note: "HTTP error 404" would match CONNECTION_ERROR first
test_cases = [
"Permission denied",
"File xyz not found",
"Cannot write to file",
"Disk is full",
"No module named local_deep_research",
"Attempt to write readonly database",
"database is locked",
"database table is locked",
]
for error_msg in test_cases:
category = reporter.categorize_error(error_msg)
assert category == ErrorCategory.FILE_ERROR, (
f"Failed for: {error_msg}"
)
class TestServiceNameExtraction:
"""Tests for service name extraction from errors."""
@pytest.fixture
def reporter(self):
"""Create an ErrorReporter instance."""
from local_deep_research.error_handling.error_reporter import (
ErrorReporter,
)
return ErrorReporter()
def test_extract_service_names(self, reporter):
"""Service names are extracted from error messages."""
test_cases = [
("OpenAI API error", "Openai"),
("Anthropic rate limit", "Anthropic"),
("Google API error", "Google"),
("Ollama connection failed", "Ollama"),
("SearXNG timeout", "Searxng"),
("Tavily search failed", "Tavily"),
("Brave search error", "Brave"),
("Unknown service error", "API Service"),
]
for error_msg, expected_service in test_cases:
actual = reporter._extract_service_name(error_msg)
assert actual == expected_service, f"Failed for: {error_msg}"