Files
learningcircuit--local-deep…/tests/news/test_exceptions_comprehensive.py
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

697 lines
24 KiB
Python

"""
Comprehensive tests for news module exceptions.
Tests all exception classes, their attributes, methods, and inheritance.
"""
import pytest
class TestNewsAPIExceptionBase:
"""Tests for NewsAPIException base class."""
def test_creates_with_message_only(self):
"""Test creating exception with just message."""
from local_deep_research.news.exceptions import NewsAPIException
exc = NewsAPIException("Test error")
assert exc.message == "Test error"
assert str(exc) == "Test error"
def test_default_status_code_is_500(self):
"""Test default status code is 500."""
from local_deep_research.news.exceptions import NewsAPIException
exc = NewsAPIException("Test error")
assert exc.status_code == 500
def test_custom_status_code(self):
"""Test setting custom status code."""
from local_deep_research.news.exceptions import NewsAPIException
exc = NewsAPIException("Not found", status_code=404)
assert exc.status_code == 404
def test_default_error_code_is_class_name(self):
"""Test default error_code is class name."""
from local_deep_research.news.exceptions import NewsAPIException
exc = NewsAPIException("Test error")
assert exc.error_code == "NewsAPIException"
def test_custom_error_code(self):
"""Test setting custom error code."""
from local_deep_research.news.exceptions import NewsAPIException
exc = NewsAPIException("Test error", error_code="CUSTOM_ERROR")
assert exc.error_code == "CUSTOM_ERROR"
def test_default_details_is_empty_dict(self):
"""Test default details is empty dict."""
from local_deep_research.news.exceptions import NewsAPIException
exc = NewsAPIException("Test error")
assert exc.details == {}
def test_custom_details(self):
"""Test setting custom details."""
from local_deep_research.news.exceptions import NewsAPIException
exc = NewsAPIException(
"Test error", details={"key": "value", "count": 42}
)
assert exc.details == {"key": "value", "count": 42}
def test_is_exception_subclass(self):
"""Test is subclass of Exception."""
from local_deep_research.news.exceptions import NewsAPIException
assert issubclass(NewsAPIException, Exception)
def test_can_be_raised(self):
"""Test exception can be raised and caught."""
from local_deep_research.news.exceptions import NewsAPIException
with pytest.raises(NewsAPIException):
raise NewsAPIException("Test error")
def test_to_dict_basic(self):
"""Test to_dict returns basic fields."""
from local_deep_research.news.exceptions import NewsAPIException
exc = NewsAPIException("Test error")
result = exc.to_dict()
assert result["error"] == "Test error"
assert result["error_code"] == "NewsAPIException"
assert result["status_code"] == 500
def test_to_dict_with_details(self):
"""Test to_dict includes details when present."""
from local_deep_research.news.exceptions import NewsAPIException
exc = NewsAPIException("Test error", details={"key": "value"})
result = exc.to_dict()
assert result["details"] == {"key": "value"}
def test_to_dict_without_details(self):
"""Test to_dict excludes details key when empty."""
from local_deep_research.news.exceptions import NewsAPIException
exc = NewsAPIException("Test error")
result = exc.to_dict()
assert "details" not in result
def test_all_parameters(self):
"""Test creating with all parameters."""
from local_deep_research.news.exceptions import NewsAPIException
exc = NewsAPIException(
message="Full error",
status_code=422,
error_code="FULL_ERROR",
details={"field": "value"},
)
assert exc.message == "Full error"
assert exc.status_code == 422
assert exc.error_code == "FULL_ERROR"
assert exc.details == {"field": "value"}
class TestNewsFeatureDisabledException:
"""Tests for NewsFeatureDisabledException."""
def test_default_message(self):
"""Test default message."""
from local_deep_research.news.exceptions import (
NewsFeatureDisabledException,
)
exc = NewsFeatureDisabledException()
assert exc.message == "News system is disabled"
def test_custom_message(self):
"""Test custom message."""
from local_deep_research.news.exceptions import (
NewsFeatureDisabledException,
)
exc = NewsFeatureDisabledException("Custom disabled message")
assert exc.message == "Custom disabled message"
def test_status_code_is_503(self):
"""Test status code is 503 (Service Unavailable)."""
from local_deep_research.news.exceptions import (
NewsFeatureDisabledException,
)
exc = NewsFeatureDisabledException()
assert exc.status_code == 503
def test_error_code(self):
"""Test error code is NEWS_DISABLED."""
from local_deep_research.news.exceptions import (
NewsFeatureDisabledException,
)
exc = NewsFeatureDisabledException()
assert exc.error_code == "NEWS_DISABLED"
def test_inherits_from_base(self):
"""Test inherits from NewsAPIException."""
from local_deep_research.news.exceptions import (
NewsFeatureDisabledException,
NewsAPIException,
)
assert issubclass(NewsFeatureDisabledException, NewsAPIException)
class TestInvalidLimitException:
"""Tests for InvalidLimitException."""
def test_message_includes_limit(self):
"""Test message includes the provided limit."""
from local_deep_research.news.exceptions import InvalidLimitException
exc = InvalidLimitException(0)
assert "0" in exc.message
assert "Limit must be at least 1" in exc.message
def test_status_code_is_400(self):
"""Test status code is 400 (Bad Request)."""
from local_deep_research.news.exceptions import InvalidLimitException
exc = InvalidLimitException(-5)
assert exc.status_code == 400
def test_error_code(self):
"""Test error code is INVALID_LIMIT."""
from local_deep_research.news.exceptions import InvalidLimitException
exc = InvalidLimitException(0)
assert exc.error_code == "INVALID_LIMIT"
def test_details_include_provided_limit(self):
"""Test details include provided_limit."""
from local_deep_research.news.exceptions import InvalidLimitException
exc = InvalidLimitException(-10)
assert exc.details["provided_limit"] == -10
def test_details_include_min_limit(self):
"""Test details include min_limit."""
from local_deep_research.news.exceptions import InvalidLimitException
exc = InvalidLimitException(0)
assert exc.details["min_limit"] == 1
def test_various_invalid_limits(self):
"""Test with various invalid limit values."""
from local_deep_research.news.exceptions import InvalidLimitException
for limit in [0, -1, -100, -999999]:
exc = InvalidLimitException(limit)
assert exc.details["provided_limit"] == limit
class TestSubscriptionNotFoundException:
"""Tests for SubscriptionNotFoundException."""
def test_message_includes_id(self):
"""Test message includes subscription ID."""
from local_deep_research.news.exceptions import (
SubscriptionNotFoundException,
)
exc = SubscriptionNotFoundException("sub-123")
assert "sub-123" in exc.message
def test_status_code_is_404(self):
"""Test status code is 404 (Not Found)."""
from local_deep_research.news.exceptions import (
SubscriptionNotFoundException,
)
exc = SubscriptionNotFoundException("sub-123")
assert exc.status_code == 404
def test_error_code(self):
"""Test error code is SUBSCRIPTION_NOT_FOUND."""
from local_deep_research.news.exceptions import (
SubscriptionNotFoundException,
)
exc = SubscriptionNotFoundException("sub-123")
assert exc.error_code == "SUBSCRIPTION_NOT_FOUND"
def test_details_include_subscription_id(self):
"""Test details include subscription_id."""
from local_deep_research.news.exceptions import (
SubscriptionNotFoundException,
)
exc = SubscriptionNotFoundException("my-sub-id")
assert exc.details["subscription_id"] == "my-sub-id"
class TestSubscriptionCreationException:
"""Tests for SubscriptionCreationException."""
def test_message_includes_reason(self):
"""Test message includes the reason."""
from local_deep_research.news.exceptions import (
SubscriptionCreationException,
)
exc = SubscriptionCreationException("duplicate key")
assert "duplicate key" in exc.message
def test_status_code_is_500(self):
"""Test status code is 500."""
from local_deep_research.news.exceptions import (
SubscriptionCreationException,
)
exc = SubscriptionCreationException("error")
assert exc.status_code == 500
def test_error_code(self):
"""Test error code is SUBSCRIPTION_CREATE_FAILED."""
from local_deep_research.news.exceptions import (
SubscriptionCreationException,
)
exc = SubscriptionCreationException("error")
assert exc.error_code == "SUBSCRIPTION_CREATE_FAILED"
def test_custom_details(self):
"""Test can pass custom details."""
from local_deep_research.news.exceptions import (
SubscriptionCreationException,
)
exc = SubscriptionCreationException(
"error", details={"query": "test", "user_id": "user1"}
)
assert exc.details["query"] == "test"
assert exc.details["user_id"] == "user1"
class TestSubscriptionUpdateException:
"""Tests for SubscriptionUpdateException."""
def test_message_includes_id_and_reason(self):
"""Test message includes subscription ID and reason."""
from local_deep_research.news.exceptions import (
SubscriptionUpdateException,
)
exc = SubscriptionUpdateException("sub-456", "invalid field")
assert "sub-456" in exc.message
assert "invalid field" in exc.message
def test_status_code_is_500(self):
"""Test status code is 500."""
from local_deep_research.news.exceptions import (
SubscriptionUpdateException,
)
exc = SubscriptionUpdateException("sub-123", "error")
assert exc.status_code == 500
def test_error_code(self):
"""Test error code is SUBSCRIPTION_UPDATE_FAILED."""
from local_deep_research.news.exceptions import (
SubscriptionUpdateException,
)
exc = SubscriptionUpdateException("sub-123", "error")
assert exc.error_code == "SUBSCRIPTION_UPDATE_FAILED"
def test_details_include_subscription_id(self):
"""Test details include subscription_id."""
from local_deep_research.news.exceptions import (
SubscriptionUpdateException,
)
exc = SubscriptionUpdateException("sub-789", "error")
assert exc.details["subscription_id"] == "sub-789"
class TestSubscriptionDeletionException:
"""Tests for SubscriptionDeletionException."""
def test_message_includes_id_and_reason(self):
"""Test message includes subscription ID and reason."""
from local_deep_research.news.exceptions import (
SubscriptionDeletionException,
)
exc = SubscriptionDeletionException("sub-111", "foreign key constraint")
assert "sub-111" in exc.message
assert "foreign key constraint" in exc.message
def test_status_code_is_500(self):
"""Test status code is 500."""
from local_deep_research.news.exceptions import (
SubscriptionDeletionException,
)
exc = SubscriptionDeletionException("sub-123", "error")
assert exc.status_code == 500
def test_error_code(self):
"""Test error code is SUBSCRIPTION_DELETE_FAILED."""
from local_deep_research.news.exceptions import (
SubscriptionDeletionException,
)
exc = SubscriptionDeletionException("sub-123", "error")
assert exc.error_code == "SUBSCRIPTION_DELETE_FAILED"
class TestDatabaseAccessException:
"""Tests for DatabaseAccessException."""
def test_message_includes_operation_and_reason(self):
"""Test message includes operation and reason."""
from local_deep_research.news.exceptions import DatabaseAccessException
exc = DatabaseAccessException("insert", "connection refused")
assert "insert" in exc.message
assert "connection refused" in exc.message
def test_status_code_is_500(self):
"""Test status code is 500."""
from local_deep_research.news.exceptions import DatabaseAccessException
exc = DatabaseAccessException("query", "error")
assert exc.status_code == 500
def test_error_code(self):
"""Test error code is DATABASE_ERROR."""
from local_deep_research.news.exceptions import DatabaseAccessException
exc = DatabaseAccessException("query", "error")
assert exc.error_code == "DATABASE_ERROR"
def test_details_include_operation(self):
"""Test details include operation."""
from local_deep_research.news.exceptions import DatabaseAccessException
exc = DatabaseAccessException("fetch_all", "timeout")
assert exc.details["operation"] == "fetch_all"
class TestNewsFeedGenerationException:
"""Tests for NewsFeedGenerationException."""
def test_message_includes_reason(self):
"""Test message includes reason."""
from local_deep_research.news.exceptions import (
NewsFeedGenerationException,
)
exc = NewsFeedGenerationException("no sources available")
assert "no sources available" in exc.message
def test_status_code_is_500(self):
"""Test status code is 500."""
from local_deep_research.news.exceptions import (
NewsFeedGenerationException,
)
exc = NewsFeedGenerationException("error")
assert exc.status_code == 500
def test_error_code(self):
"""Test error code is FEED_GENERATION_FAILED."""
from local_deep_research.news.exceptions import (
NewsFeedGenerationException,
)
exc = NewsFeedGenerationException("error")
assert exc.error_code == "FEED_GENERATION_FAILED"
def test_details_include_user_id_when_provided(self):
"""Test details include user_id when provided."""
from local_deep_research.news.exceptions import (
NewsFeedGenerationException,
)
exc = NewsFeedGenerationException("error", user_id="user123")
assert exc.details["user_id"] == "user123"
def test_details_empty_when_no_user_id(self):
"""Test details empty when no user_id provided."""
from local_deep_research.news.exceptions import (
NewsFeedGenerationException,
)
exc = NewsFeedGenerationException("error")
assert exc.details == {}
class TestResearchProcessingException:
"""Tests for ResearchProcessingException."""
def test_message_includes_reason(self):
"""Test message includes reason."""
from local_deep_research.news.exceptions import (
ResearchProcessingException,
)
exc = ResearchProcessingException("parsing failed")
assert "parsing failed" in exc.message
def test_status_code_is_500(self):
"""Test status code is 500."""
from local_deep_research.news.exceptions import (
ResearchProcessingException,
)
exc = ResearchProcessingException("error")
assert exc.status_code == 500
def test_error_code(self):
"""Test error code is RESEARCH_PROCESSING_FAILED."""
from local_deep_research.news.exceptions import (
ResearchProcessingException,
)
exc = ResearchProcessingException("error")
assert exc.error_code == "RESEARCH_PROCESSING_FAILED"
def test_details_include_research_id_when_provided(self):
"""Test details include research_id when provided."""
from local_deep_research.news.exceptions import (
ResearchProcessingException,
)
exc = ResearchProcessingException("error", research_id="res-456")
assert exc.details["research_id"] == "res-456"
class TestNotImplementedException:
"""Tests for NotImplementedException."""
def test_message_includes_feature(self):
"""Test message includes feature name."""
from local_deep_research.news.exceptions import NotImplementedException
exc = NotImplementedException("advanced_search")
assert "advanced_search" in exc.message
def test_status_code_is_501(self):
"""Test status code is 501 (Not Implemented)."""
from local_deep_research.news.exceptions import NotImplementedException
exc = NotImplementedException("feature")
assert exc.status_code == 501
def test_error_code(self):
"""Test error code is NOT_IMPLEMENTED."""
from local_deep_research.news.exceptions import NotImplementedException
exc = NotImplementedException("feature")
assert exc.error_code == "NOT_IMPLEMENTED"
def test_details_include_feature(self):
"""Test details include feature name."""
from local_deep_research.news.exceptions import NotImplementedException
exc = NotImplementedException("export_to_pdf")
assert exc.details["feature"] == "export_to_pdf"
class TestInvalidParameterException:
"""Tests for InvalidParameterException."""
def test_message_includes_parameter_and_reason(self):
"""Test message includes parameter name and reason."""
from local_deep_research.news.exceptions import (
InvalidParameterException,
)
exc = InvalidParameterException("limit", -5, "must be positive")
assert "limit" in exc.message
assert "must be positive" in exc.message
def test_status_code_is_400(self):
"""Test status code is 400."""
from local_deep_research.news.exceptions import (
InvalidParameterException,
)
exc = InvalidParameterException("param", "val", "invalid")
assert exc.status_code == 400
def test_error_code(self):
"""Test error code is INVALID_PARAMETER."""
from local_deep_research.news.exceptions import (
InvalidParameterException,
)
exc = InvalidParameterException("param", "val", "invalid")
assert exc.error_code == "INVALID_PARAMETER"
def test_details_include_parameter_and_value(self):
"""Test details include parameter and value."""
from local_deep_research.news.exceptions import (
InvalidParameterException,
)
exc = InvalidParameterException("offset", -10, "must be non-negative")
assert exc.details["parameter"] == "offset"
assert exc.details["value"] == -10
class TestSchedulerNotificationException:
"""Tests for SchedulerNotificationException."""
def test_message_includes_action_and_reason(self):
"""Test message includes action and reason."""
from local_deep_research.news.exceptions import (
SchedulerNotificationException,
)
exc = SchedulerNotificationException("start_job", "scheduler offline")
assert "start_job" in exc.message
assert "scheduler offline" in exc.message
def test_status_code_is_500(self):
"""Test status code is 500."""
from local_deep_research.news.exceptions import (
SchedulerNotificationException,
)
exc = SchedulerNotificationException("action", "error")
assert exc.status_code == 500
def test_error_code(self):
"""Test error code is SCHEDULER_NOTIFICATION_FAILED."""
from local_deep_research.news.exceptions import (
SchedulerNotificationException,
)
exc = SchedulerNotificationException("action", "error")
assert exc.error_code == "SCHEDULER_NOTIFICATION_FAILED"
def test_details_include_action(self):
"""Test details include action."""
from local_deep_research.news.exceptions import (
SchedulerNotificationException,
)
exc = SchedulerNotificationException("reschedule", "error")
assert exc.details["action"] == "reschedule"
class TestExceptionInheritanceChain:
"""Tests for exception inheritance."""
def test_all_exceptions_inherit_from_base(self):
"""Test all custom exceptions inherit from NewsAPIException."""
from local_deep_research.news.exceptions import (
NewsAPIException,
NewsFeatureDisabledException,
InvalidLimitException,
SubscriptionNotFoundException,
SubscriptionCreationException,
SubscriptionUpdateException,
SubscriptionDeletionException,
DatabaseAccessException,
NewsFeedGenerationException,
ResearchProcessingException,
NotImplementedException,
InvalidParameterException,
SchedulerNotificationException,
)
exception_classes = [
NewsFeatureDisabledException,
InvalidLimitException,
SubscriptionNotFoundException,
SubscriptionCreationException,
SubscriptionUpdateException,
SubscriptionDeletionException,
DatabaseAccessException,
NewsFeedGenerationException,
ResearchProcessingException,
NotImplementedException,
InvalidParameterException,
SchedulerNotificationException,
]
for exc_class in exception_classes:
assert issubclass(exc_class, NewsAPIException)
assert issubclass(exc_class, Exception)
class TestExceptionToDictJsonSerializable:
"""Test that to_dict output is JSON serializable."""
def test_to_dict_is_json_serializable(self):
"""Test to_dict returns JSON-serializable dict."""
import json
from local_deep_research.news.exceptions import NewsAPIException
exc = NewsAPIException(
"Test error",
status_code=400,
error_code="TEST",
details={"key": "value", "num": 123, "list": [1, 2, 3]},
)
result = exc.to_dict()
# Should not raise
json_str = json.dumps(result)
assert json_str is not None
def test_all_exceptions_json_serializable(self):
"""Test all exception to_dict outputs are JSON serializable."""
import json
from local_deep_research.news.exceptions import (
NewsFeatureDisabledException,
InvalidLimitException,
SubscriptionNotFoundException,
DatabaseAccessException,
InvalidParameterException,
)
exceptions = [
NewsFeatureDisabledException(),
InvalidLimitException(0),
SubscriptionNotFoundException("sub-123"),
DatabaseAccessException("query", "timeout"),
InvalidParameterException("param", "value", "error"),
]
for exc in exceptions:
result = exc.to_dict()
json_str = json.dumps(result)
assert json_str is not None