项目文件夹

文件
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 行
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)