""" Extended tests for news/exceptions.py Tests cover: - NewsAPIException base class - All derived exception classes - to_dict() serialization - Error codes and status codes - Exception inheritance """ class TestNewsAPIException: """Tests for NewsAPIException base class.""" def test_inherits_from_exception(self): """NewsAPIException inherits from Exception.""" from local_deep_research.news.exceptions import NewsAPIException exc = NewsAPIException("test") assert isinstance(exc, Exception) def test_stores_message(self): """Stores the message.""" from local_deep_research.news.exceptions import NewsAPIException exc = NewsAPIException("Error message") assert exc.message == "Error message" def test_default_status_code_is_500(self): """Default status_code is 500.""" from local_deep_research.news.exceptions import NewsAPIException exc = NewsAPIException("test") assert exc.status_code == 500 def test_custom_status_code(self): """Accepts custom status_code.""" from local_deep_research.news.exceptions import NewsAPIException exc = NewsAPIException("test", status_code=404) assert exc.status_code == 404 def test_default_error_code_is_class_name(self): """Default error_code is the class name.""" from local_deep_research.news.exceptions import NewsAPIException exc = NewsAPIException("test") assert exc.error_code == "NewsAPIException" def test_custom_error_code(self): """Accepts custom error_code.""" from local_deep_research.news.exceptions import NewsAPIException exc = NewsAPIException("test", error_code="CUSTOM_ERROR") assert exc.error_code == "CUSTOM_ERROR" def test_details_defaults_to_empty_dict(self): """Details defaults to empty dict.""" from local_deep_research.news.exceptions import NewsAPIException exc = NewsAPIException("test") assert exc.details == {} def test_custom_details(self): """Accepts custom details.""" from local_deep_research.news.exceptions import NewsAPIException exc = NewsAPIException("test", details={"key": "value"}) assert exc.details == {"key": "value"} def test_str_returns_message(self): """str() returns the message.""" from local_deep_research.news.exceptions import NewsAPIException exc = NewsAPIException("Error occurred") assert str(exc) == "Error occurred" class TestNewsAPIExceptionToDict: """Tests for NewsAPIException.to_dict().""" def test_includes_error(self): """Dict includes error message.""" from local_deep_research.news.exceptions import NewsAPIException exc = NewsAPIException("Error message") data = exc.to_dict() assert data["error"] == "Error message" def test_includes_error_code(self): """Dict includes error_code.""" from local_deep_research.news.exceptions import NewsAPIException exc = NewsAPIException("test", error_code="TEST_ERROR") data = exc.to_dict() assert data["error_code"] == "TEST_ERROR" def test_includes_status_code(self): """Dict includes status_code.""" from local_deep_research.news.exceptions import NewsAPIException exc = NewsAPIException("test", status_code=400) data = exc.to_dict() assert data["status_code"] == 400 def test_includes_details_when_present(self): """Dict includes details when provided.""" from local_deep_research.news.exceptions import NewsAPIException exc = NewsAPIException("test", details={"field": "value"}) data = exc.to_dict() assert data["details"] == {"field": "value"} def test_excludes_details_when_empty(self): """Dict excludes details when empty.""" from local_deep_research.news.exceptions import NewsAPIException exc = NewsAPIException("test") data = exc.to_dict() assert "details" not in data class TestNewsFeatureDisabledException: """Tests for NewsFeatureDisabledException.""" def test_inherits_from_news_api_exception(self): """Inherits from NewsAPIException.""" from local_deep_research.news.exceptions import ( NewsFeatureDisabledException, NewsAPIException, ) exc = NewsFeatureDisabledException() assert isinstance(exc, NewsAPIException) def test_default_message(self): """Has default message.""" from local_deep_research.news.exceptions import ( NewsFeatureDisabledException, ) exc = NewsFeatureDisabledException() assert exc.message == "News system is disabled" def test_custom_message(self): """Accepts custom message.""" from local_deep_research.news.exceptions import ( NewsFeatureDisabledException, ) exc = NewsFeatureDisabledException("Custom disable message") assert exc.message == "Custom disable message" def test_status_code_is_503(self): """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_is_news_disabled(self): """Error code is NEWS_DISABLED.""" from local_deep_research.news.exceptions import ( NewsFeatureDisabledException, ) exc = NewsFeatureDisabledException() assert exc.error_code == "NEWS_DISABLED" class TestInvalidLimitException: """Tests for InvalidLimitException.""" def test_inherits_from_news_api_exception(self): """Inherits from NewsAPIException.""" from local_deep_research.news.exceptions import ( InvalidLimitException, NewsAPIException, ) exc = InvalidLimitException(0) assert isinstance(exc, NewsAPIException) def test_includes_limit_in_message(self): """Message includes the invalid limit value.""" from local_deep_research.news.exceptions import InvalidLimitException exc = InvalidLimitException(-5) assert "-5" in exc.message def test_status_code_is_400(self): """Status code is 400 (Bad Request).""" from local_deep_research.news.exceptions import InvalidLimitException exc = InvalidLimitException(0) assert exc.status_code == 400 def test_error_code_is_invalid_limit(self): """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): """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): """Details include min_limit.""" from local_deep_research.news.exceptions import InvalidLimitException exc = InvalidLimitException(0) assert exc.details["min_limit"] == 1 class TestSubscriptionNotFoundException: """Tests for SubscriptionNotFoundException.""" def test_inherits_from_news_api_exception(self): """Inherits from NewsAPIException.""" from local_deep_research.news.exceptions import ( SubscriptionNotFoundException, NewsAPIException, ) exc = SubscriptionNotFoundException("sub-123") assert isinstance(exc, NewsAPIException) def test_includes_subscription_id_in_message(self): """Message includes the subscription ID.""" from local_deep_research.news.exceptions import ( SubscriptionNotFoundException, ) exc = SubscriptionNotFoundException("sub-abc-123") assert "sub-abc-123" in exc.message def test_status_code_is_404(self): """Status code is 404 (Not Found).""" from local_deep_research.news.exceptions import ( SubscriptionNotFoundException, ) exc = SubscriptionNotFoundException("sub-1") assert exc.status_code == 404 def test_error_code_is_subscription_not_found(self): """Error code is SUBSCRIPTION_NOT_FOUND.""" from local_deep_research.news.exceptions import ( SubscriptionNotFoundException, ) exc = SubscriptionNotFoundException("sub-1") assert exc.error_code == "SUBSCRIPTION_NOT_FOUND" def test_details_include_subscription_id(self): """Details include subscription_id.""" from local_deep_research.news.exceptions import ( SubscriptionNotFoundException, ) exc = SubscriptionNotFoundException("sub-xyz") assert exc.details["subscription_id"] == "sub-xyz" class TestSubscriptionCreationException: """Tests for SubscriptionCreationException.""" def test_inherits_from_news_api_exception(self): """Inherits from NewsAPIException.""" from local_deep_research.news.exceptions import ( SubscriptionCreationException, NewsAPIException, ) exc = SubscriptionCreationException("Error") assert isinstance(exc, NewsAPIException) def test_includes_message_in_error(self): """Message includes the provided message.""" from local_deep_research.news.exceptions import ( SubscriptionCreationException, ) exc = SubscriptionCreationException("Invalid data") assert "Invalid data" in exc.message def test_status_code_is_500(self): """Status code is 500 (Internal Server Error).""" from local_deep_research.news.exceptions import ( SubscriptionCreationException, ) exc = SubscriptionCreationException("Error") assert exc.status_code == 500 def test_error_code_is_subscription_create_failed(self): """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_accepts_details(self): """Accepts optional details.""" from local_deep_research.news.exceptions import ( SubscriptionCreationException, ) exc = SubscriptionCreationException("Error", details={"field": "topic"}) assert exc.details["field"] == "topic" class TestSubscriptionUpdateException: """Tests for SubscriptionUpdateException.""" def test_inherits_from_news_api_exception(self): """Inherits from NewsAPIException.""" from local_deep_research.news.exceptions import ( SubscriptionUpdateException, NewsAPIException, ) exc = SubscriptionUpdateException("sub-1", "Error") assert isinstance(exc, NewsAPIException) def test_includes_subscription_id_in_message(self): """Message includes the subscription ID.""" from local_deep_research.news.exceptions import ( SubscriptionUpdateException, ) exc = SubscriptionUpdateException("sub-123", "Update failed") assert "sub-123" in exc.message def test_includes_error_message(self): """Message includes the error message.""" from local_deep_research.news.exceptions import ( SubscriptionUpdateException, ) exc = SubscriptionUpdateException("sub-1", "Validation error") assert "Validation error" in exc.message def test_status_code_is_500(self): """Status code is 500.""" from local_deep_research.news.exceptions import ( SubscriptionUpdateException, ) exc = SubscriptionUpdateException("sub-1", "Error") assert exc.status_code == 500 def test_details_include_subscription_id(self): """Details include subscription_id.""" from local_deep_research.news.exceptions import ( SubscriptionUpdateException, ) exc = SubscriptionUpdateException("sub-xyz", "Error") assert exc.details["subscription_id"] == "sub-xyz" class TestSubscriptionDeletionException: """Tests for SubscriptionDeletionException.""" def test_inherits_from_news_api_exception(self): """Inherits from NewsAPIException.""" from local_deep_research.news.exceptions import ( SubscriptionDeletionException, NewsAPIException, ) exc = SubscriptionDeletionException("sub-1", "Error") assert isinstance(exc, NewsAPIException) def test_includes_subscription_id_in_message(self): """Message includes the subscription ID.""" from local_deep_research.news.exceptions import ( SubscriptionDeletionException, ) exc = SubscriptionDeletionException("sub-456", "Delete failed") assert "sub-456" in exc.message def test_error_code_is_subscription_delete_failed(self): """Error code is SUBSCRIPTION_DELETE_FAILED.""" from local_deep_research.news.exceptions import ( SubscriptionDeletionException, ) exc = SubscriptionDeletionException("sub-1", "Error") assert exc.error_code == "SUBSCRIPTION_DELETE_FAILED" class TestDatabaseAccessException: """Tests for DatabaseAccessException.""" def test_inherits_from_news_api_exception(self): """Inherits from NewsAPIException.""" from local_deep_research.news.exceptions import ( DatabaseAccessException, NewsAPIException, ) exc = DatabaseAccessException("query", "Connection failed") assert isinstance(exc, NewsAPIException) def test_includes_operation_in_message(self): """Message includes the operation.""" from local_deep_research.news.exceptions import DatabaseAccessException exc = DatabaseAccessException("insert", "Error") assert "insert" in exc.message def test_includes_error_message(self): """Message includes the error message.""" from local_deep_research.news.exceptions import DatabaseAccessException exc = DatabaseAccessException("op", "Connection timeout") assert "Connection timeout" in exc.message def test_error_code_is_database_error(self): """Error code is DATABASE_ERROR.""" from local_deep_research.news.exceptions import DatabaseAccessException exc = DatabaseAccessException("op", "Error") assert exc.error_code == "DATABASE_ERROR" def test_details_include_operation(self): """Details include operation.""" from local_deep_research.news.exceptions import DatabaseAccessException exc = DatabaseAccessException("update", "Error") assert exc.details["operation"] == "update" class TestNewsFeedGenerationException: """Tests for NewsFeedGenerationException.""" def test_inherits_from_news_api_exception(self): """Inherits from NewsAPIException.""" from local_deep_research.news.exceptions import ( NewsFeedGenerationException, NewsAPIException, ) exc = NewsFeedGenerationException("Error") assert isinstance(exc, NewsAPIException) def test_includes_message_in_error(self): """Message includes the provided message.""" from local_deep_research.news.exceptions import ( NewsFeedGenerationException, ) exc = NewsFeedGenerationException("No sources available") assert "No sources available" in exc.message def test_error_code_is_feed_generation_failed(self): """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_includes_user_id_when_provided(self): """Details include user_id when provided.""" from local_deep_research.news.exceptions import ( NewsFeedGenerationException, ) exc = NewsFeedGenerationException("Error", user_id="user-123") assert exc.details["user_id"] == "user-123" def test_no_user_id_when_not_provided(self): """Details empty when user_id not provided.""" from local_deep_research.news.exceptions import ( NewsFeedGenerationException, ) exc = NewsFeedGenerationException("Error") assert "user_id" not in exc.details class TestResearchProcessingException: """Tests for ResearchProcessingException.""" def test_inherits_from_news_api_exception(self): """Inherits from NewsAPIException.""" from local_deep_research.news.exceptions import ( ResearchProcessingException, NewsAPIException, ) exc = ResearchProcessingException("Error") assert isinstance(exc, NewsAPIException) def test_error_code_is_research_processing_failed(self): """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_includes_research_id_when_provided(self): """Details include research_id when provided.""" from local_deep_research.news.exceptions import ( ResearchProcessingException, ) exc = ResearchProcessingException("Error", research_id="res-123") assert exc.details["research_id"] == "res-123" class TestNotImplementedException: """Tests for NotImplementedException.""" def test_inherits_from_news_api_exception(self): """Inherits from NewsAPIException.""" from local_deep_research.news.exceptions import ( NotImplementedException, NewsAPIException, ) exc = NotImplementedException("feature") assert isinstance(exc, NewsAPIException) def test_includes_feature_in_message(self): """Message includes the feature name.""" from local_deep_research.news.exceptions import NotImplementedException exc = NotImplementedException("AI summarization") assert "AI summarization" in exc.message def test_status_code_is_501(self): """Status code is 501 (Not Implemented).""" from local_deep_research.news.exceptions import NotImplementedException exc = NotImplementedException("feature") assert exc.status_code == 501 def test_details_include_feature(self): """Details include feature.""" from local_deep_research.news.exceptions import NotImplementedException exc = NotImplementedException("real-time updates") assert exc.details["feature"] == "real-time updates" class TestInvalidParameterException: """Tests for InvalidParameterException.""" def test_inherits_from_news_api_exception(self): """Inherits from NewsAPIException.""" from local_deep_research.news.exceptions import ( InvalidParameterException, NewsAPIException, ) exc = InvalidParameterException("param", "value", "message") assert isinstance(exc, NewsAPIException) def test_includes_parameter_in_message(self): """Message includes the parameter name.""" from local_deep_research.news.exceptions import ( InvalidParameterException, ) exc = InvalidParameterException("limit", -1, "Must be positive") assert "limit" in exc.message def test_includes_error_message(self): """Message includes the error description.""" from local_deep_research.news.exceptions import ( InvalidParameterException, ) exc = InvalidParameterException("limit", -1, "Must be positive") assert "Must be positive" in exc.message def test_status_code_is_400(self): """Status code is 400 (Bad Request).""" from local_deep_research.news.exceptions import ( InvalidParameterException, ) exc = InvalidParameterException("p", "v", "m") assert exc.status_code == 400 def test_details_include_parameter(self): """Details include parameter name.""" from local_deep_research.news.exceptions import ( InvalidParameterException, ) exc = InvalidParameterException("offset", "abc", "Must be integer") assert exc.details["parameter"] == "offset" def test_details_include_value(self): """Details include the invalid value.""" from local_deep_research.news.exceptions import ( InvalidParameterException, ) exc = InvalidParameterException("offset", "abc", "Must be integer") assert exc.details["value"] == "abc" class TestSchedulerNotificationException: """Tests for SchedulerNotificationException.""" def test_inherits_from_news_api_exception(self): """Inherits from NewsAPIException.""" from local_deep_research.news.exceptions import ( SchedulerNotificationException, NewsAPIException, ) exc = SchedulerNotificationException("action", "Error") assert isinstance(exc, NewsAPIException) def test_includes_action_in_message(self): """Message includes the action.""" from local_deep_research.news.exceptions import ( SchedulerNotificationException, ) exc = SchedulerNotificationException("subscription_create", "Failed") assert "subscription_create" in exc.message def test_error_code_is_scheduler_notification_failed(self): """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): """Details include action.""" from local_deep_research.news.exceptions import ( SchedulerNotificationException, ) exc = SchedulerNotificationException("user_update", "Error") assert exc.details["action"] == "user_update"