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

408 lines
16 KiB
Python

"""Tests for report_generator module."""
from unittest.mock import MagicMock
from local_deep_research.error_handling.report_generator import (
ErrorReportGenerator,
)
from local_deep_research.error_handling.error_reporter import ErrorCategory
class TestErrorReportGeneratorInit:
"""Tests for ErrorReportGenerator initialization."""
def test_initializes_error_reporter(self, error_report_generator):
"""Should initialize with ErrorReporter."""
assert hasattr(error_report_generator, "error_reporter")
assert error_report_generator.error_reporter is not None
def test_accepts_optional_llm(self):
"""Should accept optional LLM (kept for compatibility)."""
mock_llm = MagicMock()
generator = ErrorReportGenerator(llm=mock_llm)
# LLM is not used, just accepted
assert generator is not None
class TestGenerateErrorReport:
"""Tests for generate_error_report method."""
def test_returns_markdown_string(self, error_report_generator):
"""Should return markdown string."""
result = error_report_generator.generate_error_report(
"Connection refused", "test query"
)
assert isinstance(result, str)
assert "#" in result # Has markdown headers
def test_includes_error_type(self, error_report_generator):
"""Should include error type in report."""
result = error_report_generator.generate_error_report(
"Connection refused", "test query"
)
assert "Error Type:" in result
def test_includes_what_happened(self, error_report_generator):
"""Should include what happened section."""
result = error_report_generator.generate_error_report(
"Connection refused", "test query"
)
assert "What happened:" in result
def test_includes_help_links(self, error_report_generator):
"""Should include help links."""
result = error_report_generator.generate_error_report(
"Connection refused", "test query"
)
assert "Get Help" in result
assert "Wiki" in result
assert "Discord" in result
assert "GitHub Issues" in result
def test_includes_partial_results_when_available(
self, error_report_generator, sample_partial_results
):
"""Should include partial results when available."""
result = error_report_generator.generate_error_report(
"Connection refused",
"test query",
partial_results=sample_partial_results,
)
assert "Partial Results" in result
def test_handles_no_partial_results(self, error_report_generator):
"""Should handle no partial results gracefully."""
result = error_report_generator.generate_error_report(
"Connection refused", "test query", partial_results=None
)
assert isinstance(result, str)
# Should not include partial results section
assert "Partial Results" not in result
def test_includes_research_id_when_provided(self, error_report_generator):
"""Should accept research_id parameter."""
result = error_report_generator.generate_error_report(
"Error", "query", research_id="test-uuid"
)
assert isinstance(result, str)
def test_includes_search_iterations_when_provided(
self, error_report_generator
):
"""Should accept search_iterations parameter."""
result = error_report_generator.generate_error_report(
"Error", "query", search_iterations=5
)
assert isinstance(result, str)
def test_fallback_on_exception(self, error_report_generator):
"""Should return fallback report on exception."""
# Mock error_reporter to raise exception
error_report_generator.error_reporter.analyze_error = MagicMock(
side_effect=Exception("test error")
)
result = error_report_generator.generate_error_report("Error", "query")
assert "Research Failed" in result
assert "Error report generation failed" in result
class TestFormatPartialResults:
"""Tests for _format_partial_results method."""
def test_returns_empty_for_none(self, error_report_generator):
"""Should return empty string for None."""
result = error_report_generator._format_partial_results(None)
assert result == ""
def test_returns_empty_for_empty_dict(self, error_report_generator):
"""Should return empty string for empty dict."""
result = error_report_generator._format_partial_results({})
assert result == ""
def test_formats_current_knowledge(self, error_report_generator):
"""Should format current_knowledge section."""
partial = {"current_knowledge": "This is knowledge content " * 10}
result = error_report_generator._format_partial_results(partial)
assert "Research Summary" in result
def test_truncates_long_knowledge(self, error_report_generator):
"""Should truncate knowledge over 1000 chars."""
long_content = "x" * 1500
partial = {"current_knowledge": long_content}
result = error_report_generator._format_partial_results(partial)
assert "..." in result
def test_skips_short_knowledge(self, error_report_generator):
"""Should skip knowledge under 50 chars."""
partial = {"current_knowledge": "short"}
result = error_report_generator._format_partial_results(partial)
assert "Research Summary" not in result
def test_formats_search_results(self, error_report_generator):
"""Should format search_results section."""
partial = {
"search_results": [
{"title": "Result 1", "url": "https://example.com/1"},
{"title": "Result 2", "url": "https://example.com/2"},
]
}
result = error_report_generator._format_partial_results(partial)
assert "Search Results Found" in result
assert "Result 1" in result
assert "https://example.com/1" in result
def test_limits_search_results_to_five(self, error_report_generator):
"""Should show only top 5 search results."""
partial = {
"search_results": [
{"title": f"Result {i}", "url": f"https://example.com/{i}"}
for i in range(10)
]
}
result = error_report_generator._format_partial_results(partial)
# Results are 0-indexed, so 5 results means indices 0-4
assert "Result 4" in result
assert "Result 5" not in result
def test_formats_findings(self, error_report_generator):
"""Should format findings section."""
partial = {
"findings": [
{"phase": "Phase 1", "content": "Finding content 1"},
{"phase": "Phase 2", "content": "Finding content 2"},
]
}
result = error_report_generator._format_partial_results(partial)
assert "Research Findings" in result
assert "Phase 1" in result
def test_skips_error_findings(self, error_report_generator):
"""Should skip findings that start with Error:."""
partial = {
"findings": [
{"phase": "Phase 1", "content": "Error: Something went wrong"},
{"phase": "Phase 2", "content": "Valid finding"},
]
}
result = error_report_generator._format_partial_results(partial)
assert "Valid finding" in result
# Error finding should be skipped
def test_limits_findings_to_three(self, error_report_generator):
"""Should show only top 3 findings."""
partial = {
"findings": [
{"phase": f"Phase {i}", "content": f"Finding {i}"}
for i in range(5)
]
}
result = error_report_generator._format_partial_results(partial)
assert "Phase 2" in result
assert "Phase 4" not in result
def test_truncates_long_finding_content(self, error_report_generator):
"""Should truncate finding content over 500 chars."""
partial = {"findings": [{"phase": "Phase 1", "content": "x" * 600}]}
result = error_report_generator._format_partial_results(partial)
assert "..." in result
def test_includes_note_when_results_exist(self, error_report_generator):
"""Should include note about successful collection."""
partial = {
"search_results": [
{"title": "Result", "url": "https://example.com"}
]
}
result = error_report_generator._format_partial_results(partial)
assert "successfully collected" in result
class TestGenerateQuickErrorSummary:
"""Tests for generate_quick_error_summary method."""
def test_returns_dict(self, error_report_generator):
"""Should return dictionary."""
result = error_report_generator.generate_quick_error_summary(
"Connection refused"
)
assert isinstance(result, dict)
def test_has_expected_keys(self, error_report_generator):
"""Should have expected keys."""
result = error_report_generator.generate_quick_error_summary(
"Connection refused"
)
expected_keys = ["title", "category", "severity", "recoverable"]
for key in expected_keys:
assert key in result
def test_category_is_string_value(self, error_report_generator):
"""Should return category as string value."""
result = error_report_generator.generate_quick_error_summary(
"Connection refused"
)
assert isinstance(result["category"], str)
def test_recoverable_is_boolean(self, error_report_generator):
"""Should return recoverable as boolean."""
result = error_report_generator.generate_quick_error_summary(
"Connection refused"
)
assert isinstance(result["recoverable"], bool)
class TestMakeErrorUserFriendly:
"""Tests for _make_error_user_friendly method."""
def test_replaces_max_workers_error(self, error_report_generator):
"""Should replace max_workers error with friendly message."""
result = error_report_generator._make_error_user_friendly(
"max_workers must be greater than 0"
)
assert "LLM failed to generate" in result
assert "Try this:" in result
def test_replaces_ollama_eof_error(self, error_report_generator):
"""Should replace Ollama EOF error with friendly message."""
result = error_report_generator._make_error_user_friendly(
"POST predict encountered EOF"
)
assert "Lost connection to Ollama" in result
def test_replaces_connection_refused(self, error_report_generator):
"""Should replace connection refused with friendly message."""
result = error_report_generator._make_error_user_friendly(
"Connection refused"
)
assert "Cannot connect to the LLM service" in result
def test_replaces_search_too_long(self, error_report_generator):
"""Should replace search too long error."""
result = error_report_generator._make_error_user_friendly(
"The search is longer than 256 characters"
)
assert "search query is too long" in result
def test_replaces_model_not_found(self, error_report_generator):
"""Should replace model not found error."""
result = error_report_generator._make_error_user_friendly(
"Model gemma not found in Ollama"
)
assert "model isn't available in Ollama" in result
def test_replaces_api_key_error(self, error_report_generator):
"""Should replace API key errors."""
result = error_report_generator._make_error_user_friendly(
"No auth credentials found"
)
assert "API key is missing" in result
def test_replaces_readonly_database(self, error_report_generator):
"""Should replace readonly database error."""
result = error_report_generator._make_error_user_friendly(
"Attempt to write readonly database"
)
assert "Permission issue" in result
def test_replaces_docker_networking(self, error_report_generator):
"""Should replace Docker networking errors."""
# Pattern requires "host.*localhost.*Docker" or similar
result = error_report_generator._make_error_user_friendly(
"Cannot connect to host at localhost via Docker"
)
# Should match docker pattern and suggest using host.docker.internal
assert "Docker" in result or "host.docker.internal" in result
def test_returns_original_for_unknown(self, error_report_generator):
"""Should return original message for unknown errors."""
original = "Some random error that has no pattern"
result = error_report_generator._make_error_user_friendly(original)
assert result == original
def test_includes_technical_error_in_replacement(
self, error_report_generator
):
"""Should include technical error in replaced message."""
result = error_report_generator._make_error_user_friendly(
"max_workers must be greater than 0"
)
assert "Technical error:" in result
assert "max_workers" in result
def test_case_insensitive_matching(self, error_report_generator):
"""Should match patterns case-insensitively."""
result = error_report_generator._make_error_user_friendly(
"CONNECTION REFUSED"
)
assert "Cannot connect" in result
class TestGetTechnicalContext:
"""Tests for _get_technical_context method."""
def test_returns_empty_for_no_context(self, error_report_generator):
"""Should return empty string for no context."""
error_analysis = {"category": ErrorCategory.CONNECTION_ERROR}
result = error_report_generator._get_technical_context(
error_analysis, None
)
# May still have category-based context
assert isinstance(result, str)
def test_includes_timing_info(self, error_report_generator):
"""Should include timing info when available."""
error_analysis = {"category": ErrorCategory.CONNECTION_ERROR}
partial = {
"start_time": "2024-01-01T10:00:00",
"last_activity": "2024-01-01T10:05:00",
}
result = error_report_generator._get_technical_context(
error_analysis, partial
)
assert "Start Time:" in result
def test_includes_model_info(self, error_report_generator):
"""Should include model info when available."""
error_analysis = {"category": ErrorCategory.MODEL_ERROR}
partial = {
"model_config": {"model_name": "gpt-4", "provider": "openai"}
}
result = error_report_generator._get_technical_context(
error_analysis, partial
)
assert "Model:" in result
def test_includes_search_info(self, error_report_generator):
"""Should include search info when available."""
error_analysis = {"category": ErrorCategory.SEARCH_ERROR}
partial = {"search_config": {"engine": "duckduckgo", "max_results": 10}}
result = error_report_generator._get_technical_context(
error_analysis, partial
)
assert "Search Engine:" in result
def test_includes_error_codes(self, error_report_generator):
"""Should include error codes when available."""
error_analysis = {"category": ErrorCategory.CONNECTION_ERROR}
partial = {"status_code": 503, "error_code": "SERVICE_UNAVAILABLE"}
result = error_report_generator._get_technical_context(
error_analysis, partial
)
assert "Status Code:" in result
assert "Error Code:" in result
def test_adds_category_specific_context(self, error_report_generator):
"""Should add category-specific context."""
error_analysis = {"category": ErrorCategory.CONNECTION_ERROR}
result = error_report_generator._get_technical_context(
error_analysis, {}
)
assert "Network Error:" in result
error_analysis = {"category": ErrorCategory.MODEL_ERROR}
result = error_report_generator._get_technical_context(
error_analysis, {}
)
assert "Model Error:" in result