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

179 lines
8.0 KiB
Python

"""
Test that API functions raise appropriate exceptions instead of returning error dicts.
"""
import pytest
from unittest.mock import patch, MagicMock
from local_deep_research.news import api as news_api
from local_deep_research.news.exceptions import (
InvalidLimitException,
SubscriptionNotFoundException,
SubscriptionCreationException,
DatabaseAccessException,
NotImplementedException,
)
class TestGetNewsFeedExceptions:
"""Test exception handling in get_news_feed function."""
def test_invalid_limit_raises_exception(self):
"""Test that invalid limit raises InvalidLimitException."""
with pytest.raises(InvalidLimitException) as exc_info:
news_api.get_news_feed(limit=0)
assert exc_info.value.status_code == 400
assert exc_info.value.details["provided_limit"] == 0
def test_negative_limit_raises_exception(self):
"""Test that negative limit raises InvalidLimitException."""
with pytest.raises(InvalidLimitException) as exc_info:
news_api.get_news_feed(limit=-10)
assert exc_info.value.details["provided_limit"] == -10
@patch("local_deep_research.database.session_context.get_user_db_session")
def test_database_error_raises_exception(self, mock_db_session):
"""Test that database errors raise DatabaseAccessException."""
mock_db_session.side_effect = Exception("Connection failed")
with pytest.raises(DatabaseAccessException) as exc_info:
news_api.get_news_feed(user_id="test_user")
assert exc_info.value.status_code == 500
assert "research_history_query" in exc_info.value.details["operation"]
# Raw exception text must NOT reach the client-facing message/response
# (CWE-209) — it is logged server-side instead. See _GENERIC_ERROR_DETAIL.
assert "Connection failed" not in exc_info.value.message
assert "Connection failed" not in exc_info.value.to_dict()["error"]
assert "an internal error occurred" in exc_info.value.message
class TestSubscriptionExceptions:
"""Test exception handling in subscription functions."""
@patch("local_deep_research.database.session_context.get_user_db_session")
def test_get_subscription_not_found(self, mock_db_session):
"""Test that missing subscription raises SubscriptionNotFoundException."""
mock_session = MagicMock()
mock_db_session.return_value.__enter__.return_value = mock_session
mock_session.query().filter_by().first.return_value = None
with pytest.raises(SubscriptionNotFoundException) as exc_info:
news_api.get_subscription("non-existent-id")
assert exc_info.value.status_code == 404
assert exc_info.value.details["subscription_id"] == "non-existent-id"
@patch("local_deep_research.database.session_context.get_user_db_session")
def test_get_subscription_database_error(self, mock_db_session):
"""Test that database errors in get_subscription raise DatabaseAccessException."""
mock_db_session.side_effect = Exception("Database locked")
with pytest.raises(DatabaseAccessException) as exc_info:
news_api.get_subscription("sub-123")
assert exc_info.value.status_code == 500
assert "get_subscription" in exc_info.value.details["operation"]
@patch("local_deep_research.database.session_context.get_user_db_session")
def test_update_subscription_not_found(self, mock_db_session):
"""Test that updating non-existent subscription raises exception."""
mock_session = MagicMock()
mock_db_session.return_value.__enter__.return_value = mock_session
mock_session.query().filter_by().first.return_value = None
with pytest.raises(SubscriptionNotFoundException) as exc_info:
news_api.update_subscription("non-existent", {"name": "New Name"})
assert exc_info.value.details["subscription_id"] == "non-existent"
@patch("local_deep_research.database.session_context.get_user_db_session")
def test_delete_subscription_not_found(self, mock_db_session):
"""Test that deleting non-existent subscription raises exception."""
mock_session = MagicMock()
mock_db_session.return_value.__enter__.return_value = mock_session
mock_session.query().filter_by().first.return_value = None
with pytest.raises(SubscriptionNotFoundException) as exc_info:
news_api.delete_subscription("non-existent")
assert exc_info.value.details["subscription_id"] == "non-existent"
@patch("local_deep_research.database.session_context.get_user_db_session")
def test_create_subscription_database_error(self, mock_db_session):
"""Test that database error during creation raises exception."""
mock_db_session.side_effect = Exception("Constraint violation")
with pytest.raises(SubscriptionCreationException) as exc_info:
news_api.create_subscription(
user_id="test_user",
query="test query",
subscription_type="search",
)
assert exc_info.value.status_code == 500
# Raw exception text must NOT reach the client-facing message (CWE-209).
assert "Constraint violation" not in exc_info.value.message
assert "Constraint violation" not in exc_info.value.to_dict()["error"]
assert "an internal error occurred" in exc_info.value.message
assert exc_info.value.details["query"] == "test query"
class TestNotImplementedExceptions:
"""Test that unimplemented features raise NotImplementedException."""
def test_research_news_item_not_implemented(self):
"""Test that research_news_item raises NotImplementedException."""
with pytest.raises(NotImplementedException) as exc_info:
news_api.research_news_item("card-123", "detailed")
assert exc_info.value.details["feature"] == "research_news_item"
def test_save_preferences_not_implemented(self):
"""Test that save_news_preferences raises NotImplementedException."""
with pytest.raises(NotImplementedException) as exc_info:
news_api.save_news_preferences("user", {"theme": "dark"})
assert exc_info.value.details["feature"] == "save_news_preferences"
def test_get_categories_not_implemented(self):
"""Test that get_news_categories raises NotImplementedException."""
with pytest.raises(NotImplementedException) as exc_info:
news_api.get_news_categories()
assert exc_info.value.details["feature"] == "get_news_categories"
class TestSubscriptionHistoryExceptions:
"""Test exception handling in subscription history functions."""
@patch("local_deep_research.database.session_context.get_user_db_session")
def test_subscription_history_not_found(self, mock_db_session):
"""Test that history for non-existent subscription raises exception."""
mock_session = MagicMock()
mock_db_session.return_value.__enter__.return_value = mock_session
# First call for subscription lookup returns None
mock_session.query().filter_by().first.return_value = None
with pytest.raises(SubscriptionNotFoundException) as exc_info:
news_api.get_subscription_history("non-existent")
assert exc_info.value.details["subscription_id"] == "non-existent"
@patch("local_deep_research.database.session_context.get_user_db_session")
def test_subscription_history_database_error(self, mock_db_session):
"""Test that database error in history raises exception."""
mock_db_session.side_effect = Exception("Query timeout")
with pytest.raises(DatabaseAccessException) as exc_info:
news_api.get_subscription_history("sub-123")
assert "get_subscription_history" in exc_info.value.details["operation"]
# Raw exception text must NOT reach the client-facing message (CWE-209).
assert "Query timeout" not in exc_info.value.message
assert "Query timeout" not in exc_info.value.to_dict()["error"]
assert "an internal error occurred" in exc_info.value.message