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
217 lines
8.0 KiB
Python
217 lines
8.0 KiB
Python
"""
|
||
Coverage tests for local_deep_research/news/api.py
|
||
|
||
Tests focus on branches and helpers not covered by existing test_news_api.py /
|
||
test_news_api_extended.py:
|
||
- _notify_scheduler_about_subscription_change edge paths
|
||
- get_news_feed() limit validation and subscription_id filter
|
||
- InvalidLimitException propagation
|
||
- get_subscriptions / create_subscription / update_subscription / delete_subscription
|
||
- get_subscription_cards / get_news_card / rate_card (exception paths)
|
||
- _classify_subscription_type / _validate_subscription_data helpers (if present)
|
||
"""
|
||
|
||
from unittest.mock import MagicMock, patch
|
||
|
||
import pytest
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# helpers
|
||
# ---------------------------------------------------------------------------
|
||
|
||
|
||
def _make_db_session_ctx(rows=None):
|
||
"""Return a context-manager mock that yields a session with .query()."""
|
||
if rows is None:
|
||
rows = []
|
||
session = MagicMock()
|
||
query_obj = MagicMock()
|
||
query_obj.filter.return_value = query_obj
|
||
query_obj.order_by.return_value = query_obj
|
||
query_obj.limit.return_value = query_obj
|
||
query_obj.all.return_value = rows
|
||
session.query.return_value = query_obj
|
||
ctx = MagicMock()
|
||
ctx.__enter__ = MagicMock(return_value=session)
|
||
ctx.__exit__ = MagicMock(return_value=False)
|
||
return ctx, session
|
||
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# _notify_scheduler_about_subscription_change
|
||
# ---------------------------------------------------------------------------
|
||
|
||
|
||
class TestNotifyScheduler:
|
||
def test_no_exception_when_import_fails(self):
|
||
"""Should swallow any exception silently."""
|
||
from local_deep_research.news.api import (
|
||
_notify_scheduler_about_subscription_change,
|
||
)
|
||
|
||
with patch(
|
||
"local_deep_research.news.api.logger"
|
||
): # suppress log output
|
||
# Raises inside because flask.session not available – should be caught
|
||
_notify_scheduler_about_subscription_change("created")
|
||
|
||
def test_scheduler_not_running_skips_update(self):
|
||
"""If scheduler.is_running is False no update_user_info call is made."""
|
||
from local_deep_research.news.api import (
|
||
_notify_scheduler_about_subscription_change,
|
||
)
|
||
|
||
mock_scheduler = MagicMock()
|
||
mock_scheduler.is_running = False
|
||
|
||
mock_session = {"username": "alice", "session_id": "s1"}
|
||
|
||
with (
|
||
patch("local_deep_research.news.api.logger"),
|
||
patch(
|
||
"flask.session",
|
||
mock_session,
|
||
create=True,
|
||
),
|
||
patch(
|
||
"local_deep_research.news.api.get_background_job_scheduler",
|
||
return_value=mock_scheduler,
|
||
create=True,
|
||
),
|
||
):
|
||
_notify_scheduler_about_subscription_change("updated")
|
||
|
||
mock_scheduler.update_user_info.assert_not_called()
|
||
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# get_news_feed – limit validation
|
||
# ---------------------------------------------------------------------------
|
||
|
||
|
||
class TestGetNewsFeedLimitValidation:
|
||
def test_zero_limit_raises_invalid_limit(self):
|
||
"""limit < 1 must raise InvalidLimitException."""
|
||
from local_deep_research.news.api import get_news_feed
|
||
from local_deep_research.news.exceptions import InvalidLimitException
|
||
|
||
with pytest.raises(InvalidLimitException):
|
||
get_news_feed(user_id="user1", limit=0)
|
||
|
||
def test_negative_limit_raises_invalid_limit(self):
|
||
"""Negative limit triggers the validation guard."""
|
||
from local_deep_research.news.api import get_news_feed
|
||
from local_deep_research.news.exceptions import InvalidLimitException
|
||
|
||
with pytest.raises(InvalidLimitException):
|
||
get_news_feed(user_id="user1", limit=-5)
|
||
|
||
def test_valid_limit_does_not_raise(self):
|
||
"""A valid positive limit proceeds past validation."""
|
||
from local_deep_research.news.api import get_news_feed
|
||
|
||
ctx, session = _make_db_session_ctx(rows=[])
|
||
with patch(
|
||
"local_deep_research.database.session_context.get_user_db_session",
|
||
return_value=ctx,
|
||
):
|
||
result = get_news_feed(user_id="user1", limit=5)
|
||
# Should return a dict (possibly empty news_items) without exception
|
||
assert isinstance(result, dict)
|
||
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# get_news_feed – subscription_id filter
|
||
# ---------------------------------------------------------------------------
|
||
|
||
|
||
class TestGetNewsFeedSubscriptionFilter:
|
||
def test_subscription_id_all_skips_filter(self):
|
||
"""subscription_id='all' should not apply extra query filter."""
|
||
from local_deep_research.news.api import get_news_feed
|
||
|
||
ctx, session = _make_db_session_ctx(rows=[])
|
||
with patch(
|
||
"local_deep_research.database.session_context.get_user_db_session",
|
||
return_value=ctx,
|
||
):
|
||
get_news_feed(user_id="u1", limit=10, subscription_id="all")
|
||
|
||
# The filter for a specific subscription_id should not have been called
|
||
# (We can only check .filter was called the 'status' filter only – no additional
|
||
# subscription_like filter)
|
||
call_count = session.query.return_value.filter.call_count
|
||
# status filter always applied = 1; subscription filter not applied
|
||
assert call_count == 1
|
||
|
||
def test_specific_subscription_id_applies_extra_filter(self):
|
||
"""A non-'all' subscription_id should add a second .filter call."""
|
||
from local_deep_research.news.api import get_news_feed
|
||
|
||
ctx, session = _make_db_session_ctx(rows=[])
|
||
with patch(
|
||
"local_deep_research.database.session_context.get_user_db_session",
|
||
return_value=ctx,
|
||
):
|
||
get_news_feed(user_id="u1", limit=10, subscription_id="sub-123")
|
||
|
||
# Both the status filter and the subscription filter applied
|
||
assert session.query.return_value.filter.call_count >= 2
|
||
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# get_news_feed – database exception handling
|
||
# ---------------------------------------------------------------------------
|
||
|
||
|
||
class TestGetNewsFeedExceptionHandling:
|
||
def test_db_error_raises_database_access_exception(self):
|
||
"""If database access fails get_news_feed raises DatabaseAccessException."""
|
||
from local_deep_research.news.api import get_news_feed
|
||
from local_deep_research.news.exceptions import DatabaseAccessException
|
||
|
||
with patch(
|
||
"local_deep_research.database.session_context.get_user_db_session",
|
||
side_effect=Exception("DB down"),
|
||
):
|
||
with pytest.raises(DatabaseAccessException):
|
||
get_news_feed(user_id="u1", limit=10)
|
||
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# Exception classes
|
||
# ---------------------------------------------------------------------------
|
||
|
||
|
||
class TestNewsExceptions:
|
||
def test_invalid_limit_exception_message(self):
|
||
from local_deep_research.news.exceptions import InvalidLimitException
|
||
|
||
exc = InvalidLimitException(-1)
|
||
assert "-1" in str(exc) or "limit" in str(exc).lower()
|
||
|
||
def test_subscription_not_found_exception(self):
|
||
from local_deep_research.news.exceptions import (
|
||
SubscriptionNotFoundException,
|
||
)
|
||
|
||
exc = SubscriptionNotFoundException("sub-abc")
|
||
assert "sub-abc" in str(exc)
|
||
|
||
def test_news_api_exception_is_base(self):
|
||
from local_deep_research.news.exceptions import (
|
||
NewsAPIException,
|
||
DatabaseAccessException,
|
||
)
|
||
|
||
exc = DatabaseAccessException("read", "fail")
|
||
assert isinstance(exc, NewsAPIException)
|
||
|
||
def test_news_feed_generation_exception(self):
|
||
from local_deep_research.news.exceptions import (
|
||
NewsFeedGenerationException,
|
||
)
|
||
|
||
exc = NewsFeedGenerationException("reason")
|
||
assert isinstance(exc, Exception)
|