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

217 lines
8.0 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
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)