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
856 lines
28 KiB
Python
856 lines
28 KiB
Python
"""
|
|
Extended tests for news/recommender/topic_based.py
|
|
|
|
Tests cover:
|
|
- TopicBasedRecommender initialization
|
|
- generate_recommendations() method
|
|
- _get_trending_topics() method
|
|
- _filter_topics_by_preferences() method
|
|
- _generate_topic_query() method
|
|
- _create_recommendation_card() method
|
|
- SearchBasedRecommender class
|
|
- Edge cases and error handling
|
|
"""
|
|
|
|
from unittest.mock import MagicMock, patch
|
|
|
|
|
|
class TestTopicBasedRecommenderInit:
|
|
"""Tests for TopicBasedRecommender initialization."""
|
|
|
|
def test_init_sets_max_recommendations_default(self):
|
|
"""Default max_recommendations is 5."""
|
|
from local_deep_research.news.recommender.topic_based import (
|
|
TopicBasedRecommender,
|
|
)
|
|
|
|
recommender = TopicBasedRecommender()
|
|
assert recommender.max_recommendations == 5
|
|
|
|
def test_init_inherits_from_base_recommender(self):
|
|
"""TopicBasedRecommender inherits from BaseRecommender."""
|
|
from local_deep_research.news.recommender.topic_based import (
|
|
TopicBasedRecommender,
|
|
)
|
|
from local_deep_research.news.recommender.base_recommender import (
|
|
BaseRecommender,
|
|
)
|
|
|
|
recommender = TopicBasedRecommender()
|
|
assert isinstance(recommender, BaseRecommender)
|
|
|
|
def test_init_accepts_kwargs(self):
|
|
"""Init accepts and passes kwargs to parent."""
|
|
from local_deep_research.news.recommender.topic_based import (
|
|
TopicBasedRecommender,
|
|
)
|
|
|
|
mock_registry = MagicMock()
|
|
recommender = TopicBasedRecommender(topic_registry=mock_registry)
|
|
assert recommender.topic_registry is mock_registry
|
|
|
|
def test_init_has_strategy_name(self):
|
|
"""Recommender has a strategy name attribute."""
|
|
from local_deep_research.news.recommender.topic_based import (
|
|
TopicBasedRecommender,
|
|
)
|
|
|
|
recommender = TopicBasedRecommender()
|
|
assert hasattr(recommender, "strategy_name")
|
|
|
|
|
|
class TestTopicBasedRecommenderGenerateRecommendations:
|
|
"""Tests for TopicBasedRecommender.generate_recommendations() method."""
|
|
|
|
def test_returns_list(self):
|
|
"""Returns a list of recommendations."""
|
|
from local_deep_research.news.recommender.topic_based import (
|
|
TopicBasedRecommender,
|
|
)
|
|
|
|
recommender = TopicBasedRecommender()
|
|
|
|
with patch.object(recommender, "_get_trending_topics", return_value=[]):
|
|
with patch.object(
|
|
recommender, "_get_user_preferences", return_value={}
|
|
):
|
|
result = recommender.generate_recommendations("user123")
|
|
|
|
assert isinstance(result, list)
|
|
|
|
def test_calls_get_trending_topics(self):
|
|
"""Calls _get_trending_topics with context."""
|
|
from local_deep_research.news.recommender.topic_based import (
|
|
TopicBasedRecommender,
|
|
)
|
|
|
|
recommender = TopicBasedRecommender()
|
|
context = {"current_category": "Technology"}
|
|
|
|
with patch.object(
|
|
recommender, "_get_trending_topics", return_value=[]
|
|
) as mock_get:
|
|
with patch.object(
|
|
recommender, "_get_user_preferences", return_value={}
|
|
):
|
|
recommender.generate_recommendations("user123", context=context)
|
|
|
|
mock_get.assert_called_once_with(context)
|
|
|
|
def test_calls_get_user_preferences(self):
|
|
"""Calls _get_user_preferences with user_id."""
|
|
from local_deep_research.news.recommender.topic_based import (
|
|
TopicBasedRecommender,
|
|
)
|
|
|
|
recommender = TopicBasedRecommender()
|
|
|
|
with patch.object(recommender, "_get_trending_topics", return_value=[]):
|
|
with patch.object(
|
|
recommender, "_get_user_preferences", return_value={}
|
|
) as mock_get:
|
|
recommender.generate_recommendations("user-456")
|
|
|
|
mock_get.assert_called_once_with("user-456")
|
|
|
|
def test_filters_topics_by_preferences(self):
|
|
"""Calls _filter_topics_by_preferences."""
|
|
from local_deep_research.news.recommender.topic_based import (
|
|
TopicBasedRecommender,
|
|
)
|
|
|
|
recommender = TopicBasedRecommender()
|
|
topics = ["Topic A", "Topic B"]
|
|
preferences = {"interests": {"tech": 1.5}}
|
|
|
|
with patch.object(
|
|
recommender, "_get_trending_topics", return_value=topics
|
|
):
|
|
with patch.object(
|
|
recommender, "_get_user_preferences", return_value=preferences
|
|
):
|
|
with patch.object(
|
|
recommender,
|
|
"_filter_topics_by_preferences",
|
|
return_value=[],
|
|
) as mock_filter:
|
|
recommender.generate_recommendations("user123")
|
|
|
|
mock_filter.assert_called_once_with(topics, preferences)
|
|
|
|
def test_limits_to_max_recommendations(self):
|
|
"""Only processes up to max_recommendations topics."""
|
|
from local_deep_research.news.recommender.topic_based import (
|
|
TopicBasedRecommender,
|
|
)
|
|
|
|
recommender = TopicBasedRecommender()
|
|
recommender.max_recommendations = 2
|
|
|
|
topics = ["Topic1", "Topic2", "Topic3", "Topic4", "Topic5"]
|
|
cards_created = []
|
|
|
|
def track_creation(*args, **kwargs):
|
|
cards_created.append(args[0])
|
|
return
|
|
|
|
with patch.object(
|
|
recommender, "_get_trending_topics", return_value=topics
|
|
):
|
|
with patch.object(
|
|
recommender, "_get_user_preferences", return_value={}
|
|
):
|
|
with patch.object(
|
|
recommender,
|
|
"_filter_topics_by_preferences",
|
|
return_value=topics,
|
|
):
|
|
with patch.object(
|
|
recommender,
|
|
"_create_recommendation_card",
|
|
side_effect=track_creation,
|
|
):
|
|
recommender.generate_recommendations("user123")
|
|
|
|
# Should only create cards for first 2 topics
|
|
assert len(cards_created) == 2
|
|
|
|
def test_handles_create_recommendation_exception(self):
|
|
"""Handles exception in _create_recommendation_card gracefully."""
|
|
from local_deep_research.news.recommender.topic_based import (
|
|
TopicBasedRecommender,
|
|
)
|
|
|
|
recommender = TopicBasedRecommender()
|
|
|
|
with patch.object(
|
|
recommender, "_get_trending_topics", return_value=["Topic"]
|
|
):
|
|
with patch.object(
|
|
recommender, "_get_user_preferences", return_value={}
|
|
):
|
|
with patch.object(
|
|
recommender,
|
|
"_filter_topics_by_preferences",
|
|
return_value=["Topic"],
|
|
):
|
|
with patch.object(
|
|
recommender,
|
|
"_create_recommendation_card",
|
|
side_effect=Exception("Search failed"),
|
|
):
|
|
# Should not raise
|
|
result = recommender.generate_recommendations("user123")
|
|
|
|
assert result == []
|
|
|
|
def test_updates_progress(self):
|
|
"""Updates progress during recommendation generation."""
|
|
from local_deep_research.news.recommender.topic_based import (
|
|
TopicBasedRecommender,
|
|
)
|
|
|
|
recommender = TopicBasedRecommender()
|
|
progress_updates = []
|
|
|
|
def track_progress(message, percent, metadata=None):
|
|
progress_updates.append((message, percent))
|
|
|
|
recommender.progress_callback = track_progress
|
|
|
|
with patch.object(recommender, "_get_trending_topics", return_value=[]):
|
|
with patch.object(
|
|
recommender, "_get_user_preferences", return_value={}
|
|
):
|
|
recommender.generate_recommendations("user123")
|
|
|
|
# Should have progress updates
|
|
assert len(progress_updates) > 0
|
|
|
|
def test_returns_empty_on_exception(self):
|
|
"""Returns empty list on exception."""
|
|
from local_deep_research.news.recommender.topic_based import (
|
|
TopicBasedRecommender,
|
|
)
|
|
|
|
recommender = TopicBasedRecommender()
|
|
|
|
with patch.object(
|
|
recommender,
|
|
"_get_trending_topics",
|
|
side_effect=Exception("DB error"),
|
|
):
|
|
result = recommender.generate_recommendations("user123")
|
|
|
|
assert result == []
|
|
|
|
def test_appends_valid_cards(self):
|
|
"""Appends valid cards to recommendations."""
|
|
from local_deep_research.news.recommender.topic_based import (
|
|
TopicBasedRecommender,
|
|
)
|
|
|
|
recommender = TopicBasedRecommender()
|
|
mock_card = MagicMock()
|
|
|
|
with patch.object(
|
|
recommender, "_get_trending_topics", return_value=["Topic A"]
|
|
):
|
|
with patch.object(
|
|
recommender, "_get_user_preferences", return_value={}
|
|
):
|
|
with patch.object(
|
|
recommender,
|
|
"_filter_topics_by_preferences",
|
|
return_value=["Topic A"],
|
|
):
|
|
with patch.object(
|
|
recommender,
|
|
"_create_recommendation_card",
|
|
return_value=mock_card,
|
|
):
|
|
with patch.object(
|
|
recommender,
|
|
"_sort_by_relevance",
|
|
return_value=[mock_card],
|
|
):
|
|
result = recommender.generate_recommendations(
|
|
"user123"
|
|
)
|
|
|
|
assert len(result) == 1
|
|
assert result[0] is mock_card
|
|
|
|
|
|
class TestGetTrendingTopics:
|
|
"""Tests for TopicBasedRecommender._get_trending_topics() method."""
|
|
|
|
def test_returns_list(self):
|
|
"""Returns a list of topics."""
|
|
from local_deep_research.news.recommender.topic_based import (
|
|
TopicBasedRecommender,
|
|
)
|
|
|
|
recommender = TopicBasedRecommender()
|
|
result = recommender._get_trending_topics(None)
|
|
|
|
assert isinstance(result, list)
|
|
|
|
def test_gets_topics_from_registry(self):
|
|
"""Gets topics from topic_registry if available."""
|
|
from local_deep_research.news.recommender.topic_based import (
|
|
TopicBasedRecommender,
|
|
)
|
|
|
|
mock_registry = MagicMock()
|
|
mock_registry.get_trending_topics.return_value = ["AI", "Climate"]
|
|
|
|
recommender = TopicBasedRecommender(topic_registry=mock_registry)
|
|
result = recommender._get_trending_topics(None)
|
|
|
|
mock_registry.get_trending_topics.assert_called_once_with(
|
|
hours=24, limit=20
|
|
)
|
|
assert "AI" in result
|
|
assert "Climate" in result
|
|
|
|
def test_adds_context_topics(self):
|
|
"""Adds topics from context if provided."""
|
|
from local_deep_research.news.recommender.topic_based import (
|
|
TopicBasedRecommender,
|
|
)
|
|
|
|
recommender = TopicBasedRecommender()
|
|
context = {"current_news_topics": ["Topic X", "Topic Y"]}
|
|
|
|
result = recommender._get_trending_topics(context)
|
|
|
|
assert "Topic X" in result
|
|
assert "Topic Y" in result
|
|
|
|
def test_returns_defaults_when_no_topics(self):
|
|
"""Returns default topics when none found."""
|
|
from local_deep_research.news.recommender.topic_based import (
|
|
TopicBasedRecommender,
|
|
)
|
|
|
|
recommender = TopicBasedRecommender()
|
|
result = recommender._get_trending_topics(None)
|
|
|
|
# Should have default topics
|
|
assert len(result) > 0
|
|
assert "artificial intelligence developments" in result
|
|
|
|
def test_handles_empty_context(self):
|
|
"""Handles empty context dict."""
|
|
from local_deep_research.news.recommender.topic_based import (
|
|
TopicBasedRecommender,
|
|
)
|
|
|
|
recommender = TopicBasedRecommender()
|
|
result = recommender._get_trending_topics({})
|
|
|
|
assert isinstance(result, list)
|
|
|
|
def test_combines_registry_and_context_topics(self):
|
|
"""Combines topics from registry and context."""
|
|
from local_deep_research.news.recommender.topic_based import (
|
|
TopicBasedRecommender,
|
|
)
|
|
|
|
mock_registry = MagicMock()
|
|
mock_registry.get_trending_topics.return_value = ["Registry Topic"]
|
|
|
|
recommender = TopicBasedRecommender(topic_registry=mock_registry)
|
|
context = {"current_news_topics": ["Context Topic"]}
|
|
|
|
result = recommender._get_trending_topics(context)
|
|
|
|
assert "Registry Topic" in result
|
|
assert "Context Topic" in result
|
|
|
|
|
|
class TestFilterTopicsByPreferences:
|
|
"""Tests for TopicBasedRecommender._filter_topics_by_preferences() method."""
|
|
|
|
def test_returns_list(self):
|
|
"""Returns a filtered list."""
|
|
from local_deep_research.news.recommender.topic_based import (
|
|
TopicBasedRecommender,
|
|
)
|
|
|
|
recommender = TopicBasedRecommender()
|
|
result = recommender._filter_topics_by_preferences(["Topic A"], {})
|
|
|
|
assert isinstance(result, list)
|
|
|
|
def test_filters_out_disliked_topics(self):
|
|
"""Filters out topics matching disliked_topics."""
|
|
from local_deep_research.news.recommender.topic_based import (
|
|
TopicBasedRecommender,
|
|
)
|
|
|
|
recommender = TopicBasedRecommender()
|
|
topics = ["AI News", "Sports Update", "Tech Report"]
|
|
preferences = {"disliked_topics": ["sports"]}
|
|
|
|
result = recommender._filter_topics_by_preferences(topics, preferences)
|
|
|
|
assert "AI News" in result
|
|
assert "Tech Report" in result
|
|
assert "Sports Update" not in result
|
|
|
|
def test_case_insensitive_filtering(self):
|
|
"""Filtering is case-insensitive."""
|
|
from local_deep_research.news.recommender.topic_based import (
|
|
TopicBasedRecommender,
|
|
)
|
|
|
|
recommender = TopicBasedRecommender()
|
|
topics = ["POLITICS Today", "Technology News"]
|
|
preferences = {"disliked_topics": ["Politics"]}
|
|
|
|
result = recommender._filter_topics_by_preferences(topics, preferences)
|
|
|
|
assert "POLITICS Today" not in result
|
|
assert "Technology News" in result
|
|
|
|
def test_boosts_topics_by_interests(self):
|
|
"""Topics matching interests are sorted higher."""
|
|
from local_deep_research.news.recommender.topic_based import (
|
|
TopicBasedRecommender,
|
|
)
|
|
|
|
recommender = TopicBasedRecommender()
|
|
topics = ["General News", "AI Development", "Weather Update"]
|
|
preferences = {"interests": {"ai": 2.0}}
|
|
|
|
result = recommender._filter_topics_by_preferences(topics, preferences)
|
|
|
|
# AI Development should be first due to boost
|
|
assert result[0] == "AI Development"
|
|
|
|
def test_empty_preferences(self):
|
|
"""Handles empty preferences dict."""
|
|
from local_deep_research.news.recommender.topic_based import (
|
|
TopicBasedRecommender,
|
|
)
|
|
|
|
recommender = TopicBasedRecommender()
|
|
topics = ["Topic A", "Topic B"]
|
|
|
|
result = recommender._filter_topics_by_preferences(topics, {})
|
|
|
|
assert len(result) == 2
|
|
|
|
def test_empty_topics(self):
|
|
"""Handles empty topics list."""
|
|
from local_deep_research.news.recommender.topic_based import (
|
|
TopicBasedRecommender,
|
|
)
|
|
|
|
recommender = TopicBasedRecommender()
|
|
preferences = {"interests": {"tech": 1.5}}
|
|
|
|
result = recommender._filter_topics_by_preferences([], preferences)
|
|
|
|
assert result == []
|
|
|
|
def test_partial_match_filtering(self):
|
|
"""Partial match filters topics."""
|
|
from local_deep_research.news.recommender.topic_based import (
|
|
TopicBasedRecommender,
|
|
)
|
|
|
|
recommender = TopicBasedRecommender()
|
|
topics = ["Cryptocurrency boom", "Crypto trading tips"]
|
|
preferences = {"disliked_topics": ["crypto"]}
|
|
|
|
result = recommender._filter_topics_by_preferences(topics, preferences)
|
|
|
|
assert len(result) == 0
|
|
|
|
|
|
class TestGenerateTopicQuery:
|
|
"""Tests for TopicBasedRecommender._generate_topic_query() method."""
|
|
|
|
def test_returns_string(self):
|
|
"""Returns a query string."""
|
|
from local_deep_research.news.recommender.topic_based import (
|
|
TopicBasedRecommender,
|
|
)
|
|
|
|
recommender = TopicBasedRecommender()
|
|
result = recommender._generate_topic_query("climate change")
|
|
|
|
assert isinstance(result, str)
|
|
|
|
def test_includes_topic(self):
|
|
"""Query includes the original topic."""
|
|
from local_deep_research.news.recommender.topic_based import (
|
|
TopicBasedRecommender,
|
|
)
|
|
|
|
recommender = TopicBasedRecommender()
|
|
result = recommender._generate_topic_query("artificial intelligence")
|
|
|
|
assert "artificial intelligence" in result
|
|
|
|
def test_adds_news_context(self):
|
|
"""Query adds news-related keywords."""
|
|
from local_deep_research.news.recommender.topic_based import (
|
|
TopicBasedRecommender,
|
|
)
|
|
|
|
recommender = TopicBasedRecommender()
|
|
result = recommender._generate_topic_query("technology")
|
|
|
|
assert "news" in result.lower()
|
|
|
|
def test_handles_empty_topic(self):
|
|
"""Handles empty topic string."""
|
|
from local_deep_research.news.recommender.topic_based import (
|
|
TopicBasedRecommender,
|
|
)
|
|
|
|
recommender = TopicBasedRecommender()
|
|
result = recommender._generate_topic_query("")
|
|
|
|
assert isinstance(result, str)
|
|
|
|
|
|
class TestCreateRecommendationCard:
|
|
"""Tests for TopicBasedRecommender._create_recommendation_card() method."""
|
|
|
|
def test_returns_none_on_search_error(self):
|
|
"""Returns None when search returns error."""
|
|
from local_deep_research.news.recommender.topic_based import (
|
|
TopicBasedRecommender,
|
|
)
|
|
|
|
recommender = TopicBasedRecommender()
|
|
|
|
with patch(
|
|
"local_deep_research.news.recommender.topic_based.AdvancedSearchSystem"
|
|
) as mock_search:
|
|
mock_instance = MagicMock()
|
|
mock_instance.analyze_topic.return_value = {
|
|
"error": "Search failed"
|
|
}
|
|
mock_search.return_value = mock_instance
|
|
|
|
result = recommender._create_recommendation_card(
|
|
"topic", "query", "user123"
|
|
)
|
|
|
|
assert result is None
|
|
|
|
def test_returns_none_when_no_news_items(self):
|
|
"""Returns None when no news items found."""
|
|
from local_deep_research.news.recommender.topic_based import (
|
|
TopicBasedRecommender,
|
|
)
|
|
|
|
recommender = TopicBasedRecommender()
|
|
|
|
with patch(
|
|
"local_deep_research.news.recommender.topic_based.AdvancedSearchSystem"
|
|
) as mock_search:
|
|
mock_instance = MagicMock()
|
|
mock_instance.analyze_topic.return_value = {"news_items": []}
|
|
mock_search.return_value = mock_instance
|
|
|
|
result = recommender._create_recommendation_card(
|
|
"topic", "query", "user123"
|
|
)
|
|
|
|
assert result is None
|
|
|
|
def test_uses_news_strategy(self):
|
|
"""Uses news search strategy."""
|
|
from local_deep_research.news.recommender.topic_based import (
|
|
TopicBasedRecommender,
|
|
)
|
|
|
|
recommender = TopicBasedRecommender()
|
|
|
|
with (
|
|
patch(
|
|
"local_deep_research.config.llm_config.get_llm",
|
|
return_value=MagicMock(),
|
|
),
|
|
patch(
|
|
"local_deep_research.config.search_config.get_search",
|
|
return_value=MagicMock(),
|
|
),
|
|
patch(
|
|
"local_deep_research.news.recommender.topic_based.AdvancedSearchSystem"
|
|
) as mock_search,
|
|
):
|
|
mock_instance = MagicMock()
|
|
mock_instance.analyze_topic.return_value = {"news_items": []}
|
|
mock_search.return_value = mock_instance
|
|
|
|
recommender._create_recommendation_card("topic", "query", "user123")
|
|
|
|
assert mock_search.call_args[1]["strategy_name"] == "news"
|
|
|
|
def test_calls_analyze_topic_with_is_news_search(self):
|
|
"""Calls analyze_topic with is_news_search=True."""
|
|
from local_deep_research.news.recommender.topic_based import (
|
|
TopicBasedRecommender,
|
|
)
|
|
|
|
recommender = TopicBasedRecommender()
|
|
|
|
with (
|
|
patch(
|
|
"local_deep_research.config.llm_config.get_llm",
|
|
return_value=MagicMock(),
|
|
),
|
|
patch(
|
|
"local_deep_research.config.search_config.get_search",
|
|
return_value=MagicMock(),
|
|
),
|
|
patch(
|
|
"local_deep_research.news.recommender.topic_based.AdvancedSearchSystem"
|
|
) as mock_search,
|
|
):
|
|
mock_instance = MagicMock()
|
|
mock_instance.analyze_topic.return_value = {"news_items": []}
|
|
mock_search.return_value = mock_instance
|
|
|
|
recommender._create_recommendation_card("topic", "query", "user123")
|
|
|
|
mock_instance.analyze_topic.assert_called_once_with(
|
|
"query", is_news_search=True
|
|
)
|
|
|
|
def test_handles_exception(self):
|
|
"""Handles exception gracefully."""
|
|
from local_deep_research.news.recommender.topic_based import (
|
|
TopicBasedRecommender,
|
|
)
|
|
|
|
recommender = TopicBasedRecommender()
|
|
|
|
with patch(
|
|
"local_deep_research.news.recommender.topic_based.AdvancedSearchSystem"
|
|
) as mock_search:
|
|
mock_search.return_value.analyze_topic.side_effect = Exception(
|
|
"API error"
|
|
)
|
|
|
|
result = recommender._create_recommendation_card(
|
|
"topic", "query", "user123"
|
|
)
|
|
|
|
assert result is None
|
|
|
|
def test_selects_highest_impact_item(self):
|
|
"""Selects news item with highest impact_score."""
|
|
from local_deep_research.news.recommender.topic_based import (
|
|
TopicBasedRecommender,
|
|
)
|
|
|
|
recommender = TopicBasedRecommender()
|
|
|
|
news_items = [
|
|
{"headline": "Low", "impact_score": 3},
|
|
{"headline": "High", "impact_score": 9},
|
|
{"headline": "Medium", "impact_score": 5},
|
|
]
|
|
|
|
with (
|
|
patch(
|
|
"local_deep_research.config.llm_config.get_llm",
|
|
return_value=MagicMock(),
|
|
),
|
|
patch(
|
|
"local_deep_research.config.search_config.get_search",
|
|
return_value=MagicMock(),
|
|
),
|
|
patch(
|
|
"local_deep_research.news.recommender.topic_based.AdvancedSearchSystem"
|
|
) as mock_search,
|
|
):
|
|
mock_instance = MagicMock()
|
|
mock_instance.analyze_topic.return_value = {
|
|
"news_items": news_items
|
|
}
|
|
mock_search.return_value = mock_instance
|
|
|
|
with patch(
|
|
"local_deep_research.news.recommender.topic_based.CardFactory"
|
|
) as mock_factory:
|
|
mock_card = MagicMock()
|
|
mock_factory.create_news_card_from_analysis.return_value = (
|
|
mock_card
|
|
)
|
|
|
|
recommender._create_recommendation_card(
|
|
"topic", "query", "user123"
|
|
)
|
|
|
|
# Check that the high impact item was used
|
|
call_args = (
|
|
mock_factory.create_news_card_from_analysis.call_args
|
|
)
|
|
assert call_args[1]["news_item"]["headline"] == "High"
|
|
|
|
|
|
class TestSearchBasedRecommender:
|
|
"""Tests for SearchBasedRecommender class."""
|
|
|
|
def test_inherits_from_base_recommender(self):
|
|
"""SearchBasedRecommender inherits from BaseRecommender."""
|
|
from local_deep_research.news.recommender.topic_based import (
|
|
SearchBasedRecommender,
|
|
)
|
|
from local_deep_research.news.recommender.base_recommender import (
|
|
BaseRecommender,
|
|
)
|
|
|
|
recommender = SearchBasedRecommender()
|
|
assert isinstance(recommender, BaseRecommender)
|
|
|
|
def test_generate_recommendations_returns_empty_list(self):
|
|
"""Returns empty list (search tracking disabled by default)."""
|
|
from local_deep_research.news.recommender.topic_based import (
|
|
SearchBasedRecommender,
|
|
)
|
|
|
|
recommender = SearchBasedRecommender()
|
|
result = recommender.generate_recommendations("user123")
|
|
|
|
assert result == []
|
|
|
|
def test_accepts_context(self):
|
|
"""Accepts optional context parameter."""
|
|
from local_deep_research.news.recommender.topic_based import (
|
|
SearchBasedRecommender,
|
|
)
|
|
|
|
recommender = SearchBasedRecommender()
|
|
context = {"some": "context"}
|
|
|
|
result = recommender.generate_recommendations(
|
|
"user123", context=context
|
|
)
|
|
|
|
assert result == []
|
|
|
|
|
|
class TestEdgeCases:
|
|
"""Edge cases for topic-based recommender."""
|
|
|
|
def test_topic_based_with_zero_max_recommendations(self):
|
|
"""Handles max_recommendations set to 0."""
|
|
from local_deep_research.news.recommender.topic_based import (
|
|
TopicBasedRecommender,
|
|
)
|
|
|
|
recommender = TopicBasedRecommender()
|
|
recommender.max_recommendations = 0
|
|
|
|
with patch.object(
|
|
recommender, "_get_trending_topics", return_value=["Topic"]
|
|
):
|
|
with patch.object(
|
|
recommender, "_get_user_preferences", return_value={}
|
|
):
|
|
with patch.object(
|
|
recommender,
|
|
"_filter_topics_by_preferences",
|
|
return_value=["Topic"],
|
|
):
|
|
result = recommender.generate_recommendations("user123")
|
|
|
|
assert result == []
|
|
|
|
def test_unicode_topics(self):
|
|
"""Handles unicode characters in topics."""
|
|
from local_deep_research.news.recommender.topic_based import (
|
|
TopicBasedRecommender,
|
|
)
|
|
|
|
recommender = TopicBasedRecommender()
|
|
topics = ["日本語トピック", "Emoji 🎉 Topic", "Ümläuts"]
|
|
preferences = {}
|
|
|
|
result = recommender._filter_topics_by_preferences(topics, preferences)
|
|
|
|
assert len(result) == 3
|
|
|
|
def test_very_long_topic(self):
|
|
"""Handles very long topic strings."""
|
|
from local_deep_research.news.recommender.topic_based import (
|
|
TopicBasedRecommender,
|
|
)
|
|
|
|
recommender = TopicBasedRecommender()
|
|
long_topic = "A" * 1000
|
|
|
|
result = recommender._generate_topic_query(long_topic)
|
|
|
|
assert long_topic in result
|
|
|
|
def test_special_characters_in_topic(self):
|
|
"""Handles special characters in topics."""
|
|
from local_deep_research.news.recommender.topic_based import (
|
|
TopicBasedRecommender,
|
|
)
|
|
|
|
recommender = TopicBasedRecommender()
|
|
topics = ["C++", "Node.js", "SQL*Plus", "Topic [1]"]
|
|
preferences = {}
|
|
|
|
result = recommender._filter_topics_by_preferences(topics, preferences)
|
|
|
|
assert len(result) == 4
|
|
|
|
def test_duplicate_topics(self):
|
|
"""Handles duplicate topics in list."""
|
|
from local_deep_research.news.recommender.topic_based import (
|
|
TopicBasedRecommender,
|
|
)
|
|
|
|
recommender = TopicBasedRecommender()
|
|
topics = ["AI", "AI", "Climate", "AI"]
|
|
preferences = {}
|
|
|
|
result = recommender._filter_topics_by_preferences(topics, preferences)
|
|
|
|
# Should keep duplicates (no deduplication in filter)
|
|
assert result.count("AI") == 3
|
|
|
|
def test_interest_weight_zero(self):
|
|
"""Handles interest with weight 0."""
|
|
from local_deep_research.news.recommender.topic_based import (
|
|
TopicBasedRecommender,
|
|
)
|
|
|
|
recommender = TopicBasedRecommender()
|
|
topics = ["AI Topic", "Other Topic"]
|
|
preferences = {"interests": {"ai": 0}}
|
|
|
|
result = recommender._filter_topics_by_preferences(topics, preferences)
|
|
|
|
# Should still include topics with 0 weight
|
|
assert len(result) == 2
|
|
|
|
def test_negative_interest_weight(self):
|
|
"""Handles negative interest weight (demotes topic)."""
|
|
from local_deep_research.news.recommender.topic_based import (
|
|
TopicBasedRecommender,
|
|
)
|
|
|
|
recommender = TopicBasedRecommender()
|
|
topics = ["AI Topic", "Other Topic"]
|
|
preferences = {"interests": {"ai": -1.0}}
|
|
|
|
result = recommender._filter_topics_by_preferences(topics, preferences)
|
|
|
|
# AI Topic should be last due to negative weight
|
|
assert result[-1] == "AI Topic"
|