Files
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

279 lines
10 KiB
Python

"""
Extended tests for the news API module.
Tests for API validation, subscription management, and feedback functions.
"""
import pytest
from unittest.mock import MagicMock, patch
from datetime import datetime, timezone, timedelta
class TestApiValidation:
"""Tests for API parameter validation."""
def test_invalid_limit_raises_exception(self):
"""Test invalid limit raises 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(limit=0)
def test_negative_limit_raises_exception(self):
"""Test negative limit raises 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(limit=-5)
class TestGetSubscriptionHelper:
"""Tests for get_subscription helper."""
@patch("local_deep_research.database.session_context.get_user_db_session")
def test_raises_for_not_found(self, mock_session):
"""Test raises SubscriptionNotFoundException when not found."""
from local_deep_research.news.api import get_subscription
from local_deep_research.news.exceptions import (
SubscriptionNotFoundException,
)
mock_db = MagicMock()
mock_session.return_value.__enter__.return_value = mock_db
mock_db.query().filter_by().first.return_value = None
with pytest.raises(SubscriptionNotFoundException):
get_subscription("nonexistent")
class TestGetSubscriptions:
"""Tests for get_subscriptions function."""
@patch("local_deep_research.database.session_context.get_user_db_session")
def test_returns_dict_with_subscriptions_key(self, mock_session):
"""Test returns dict with 'subscriptions' key."""
from local_deep_research.news.api import get_subscriptions
mock_db = MagicMock()
mock_session.return_value.__enter__.return_value = mock_db
mock_db.query().filter_by().order_by().all.return_value = []
result = get_subscriptions("user123")
assert "subscriptions" in result
@patch("local_deep_research.database.session_context.get_user_db_session")
def test_handles_empty_list(self, mock_session):
"""Test handles empty subscription list."""
from local_deep_research.news.api import get_subscriptions
mock_db = MagicMock()
mock_session.return_value.__enter__.return_value = mock_db
mock_db.query().filter_by().order_by().all.return_value = []
result = get_subscriptions("user123")
assert result["subscriptions"] == []
class TestSubmitFeedback:
"""Tests for submit_feedback function."""
def test_requires_valid_vote(self):
"""Test requires valid vote value."""
from local_deep_research.news.api import submit_feedback
# Invalid vote value should raise ValueError
with pytest.raises(ValueError):
submit_feedback("card-123", "user1", "invalid")
def test_requires_valid_up_or_down(self):
"""Test only 'up' or 'down' are valid votes."""
from local_deep_research.news.api import submit_feedback
with pytest.raises(ValueError):
submit_feedback("card-123", "user1", "neutral")
class TestGetVotesForCards:
"""Tests for get_votes_for_cards function."""
@patch("local_deep_research.database.session_context.get_user_db_session")
def test_returns_dict(self, mock_session):
"""Test returns a dictionary."""
from local_deep_research.news.api import get_votes_for_cards
mock_db = MagicMock()
mock_session.return_value.__enter__.return_value = mock_db
mock_db.query().filter().all.return_value = []
result = get_votes_for_cards(["card-1", "card-2"], "user1")
assert isinstance(result, dict)
@patch("local_deep_research.database.session_context.get_user_db_session")
def test_includes_votes_key(self, mock_session):
"""Test includes 'votes' key."""
from local_deep_research.news.api import get_votes_for_cards
mock_db = MagicMock()
mock_session.return_value.__enter__.return_value = mock_db
mock_db.query().filter().all.return_value = []
result = get_votes_for_cards(["card-1"], "user1")
assert "votes" in result
@patch("local_deep_research.database.session_context.get_user_db_session")
def test_empty_cards_returns_empty_votes(self, mock_session):
"""Test empty card list returns empty votes dict."""
from local_deep_research.news.api import get_votes_for_cards
mock_db = MagicMock()
mock_session.return_value.__enter__.return_value = mock_db
result = get_votes_for_cards([], "user1")
assert result["votes"] == {}
class TestNotImplementedFeatures:
"""Tests for not-yet-implemented features."""
def test_research_news_item_not_implemented(self):
"""Test research_news_item raises NotImplementedException."""
from local_deep_research.news.api import research_news_item
from local_deep_research.news.exceptions import NotImplementedException
with pytest.raises(NotImplementedException):
research_news_item("card-123", "detailed")
def test_save_news_preferences_not_implemented(self):
"""Test save_news_preferences raises NotImplementedException."""
from local_deep_research.news.api import save_news_preferences
from local_deep_research.news.exceptions import NotImplementedException
with pytest.raises(NotImplementedException):
save_news_preferences("user1", {"theme": "dark"})
def test_get_news_categories_not_implemented(self):
"""Test get_news_categories raises NotImplementedException."""
from local_deep_research.news.api import get_news_categories
from local_deep_research.news.exceptions import NotImplementedException
with pytest.raises(NotImplementedException):
get_news_categories()
class TestPrivateFormatTimeAgo:
"""Tests for _format_time_ago internal function."""
def test_returns_string_for_recent(self):
"""Test returns string for recent timestamp."""
from local_deep_research.news.api import _format_time_ago
# Use ISO format string as that's what the function expects
recent = datetime.now(timezone.utc).isoformat()
result = _format_time_ago(recent)
assert isinstance(result, str)
def test_returns_string_for_old_timestamp(self):
"""Test returns string for older timestamp."""
from local_deep_research.news.api import _format_time_ago
old = (datetime.now(timezone.utc) - timedelta(days=5)).isoformat()
result = _format_time_ago(old)
assert isinstance(result, str)
def test_handles_iso_format(self):
"""Test handles ISO format strings."""
from local_deep_research.news.api import _format_time_ago
iso_str = "2024-01-15T12:00:00+00:00"
result = _format_time_ago(iso_str)
assert isinstance(result, str)
def test_handles_invalid_input(self):
"""Invalid input raises (caller logs + skips the row)."""
from local_deep_research.news.api import _format_time_ago
with pytest.raises(ValueError):
_format_time_ago("not-a-date")
class TestCreateSubscription:
"""Tests for create_subscription function."""
def test_function_exists(self):
"""Test create_subscription function exists."""
from local_deep_research.news.api import create_subscription
assert callable(create_subscription)
class TestUpdateSubscription:
"""Tests for update_subscription function."""
def test_function_exists(self):
"""Test update_subscription function exists."""
from local_deep_research.news.api import update_subscription
assert callable(update_subscription)
class TestDeleteSubscription:
"""Tests for delete_subscription function."""
def test_function_exists(self):
"""Test delete_subscription function exists."""
from local_deep_research.news.api import delete_subscription
assert callable(delete_subscription)
@patch("local_deep_research.database.session_context.get_user_db_session")
def test_raises_for_not_found(self, mock_session):
"""Test raises SubscriptionNotFoundException when not found."""
from local_deep_research.news.api import delete_subscription
from local_deep_research.news.exceptions import (
SubscriptionNotFoundException,
)
mock_db = MagicMock()
mock_session.return_value.__enter__.return_value = mock_db
mock_db.query().filter_by().first.return_value = None
with pytest.raises(SubscriptionNotFoundException):
delete_subscription("nonexistent")
class TestGetSubscriptionHistory:
"""Tests for get_subscription_history function."""
def test_function_exists(self):
"""Test get_subscription_history function exists."""
from local_deep_research.news.api import get_subscription_history
assert callable(get_subscription_history)
class TestGetNewsFeed:
"""Tests for get_news_feed function."""
def test_function_exists(self):
"""Test get_news_feed function exists."""
from local_deep_research.news.api import get_news_feed
assert callable(get_news_feed)
def test_rejects_zero_limit(self):
"""Test rejects zero limit."""
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(limit=0)
def test_rejects_negative_limit(self):
"""Test rejects negative limit."""
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(limit=-10)