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

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"