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
179 lines
8.0 KiB
Python
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
|