""" Comprehensive tests for news/core/storage_manager.py Tests cover: - StorageManager initialization - User feed retrieval - Trending news - Interaction recording - Card operations - Cleanup operations """ import pytest from unittest.mock import Mock, patch class TestStorageManagerInit: """Tests for StorageManager initialization.""" def test_init_creates_instance(self): """Test that StorageManager can be initialized.""" with patch( "local_deep_research.news.core.storage_manager.get_relevance_service" ) as mock_relevance: mock_relevance.return_value = Mock() from local_deep_research.news.core.storage_manager import ( StorageManager, ) manager = StorageManager() assert manager._cards is None assert manager._ratings is None assert manager._preferences is None assert manager.relevance_service is not None class TestInteractionType: """Tests for InteractionType enum.""" def test_interaction_types_exist(self): """Test that all interaction types are defined.""" from local_deep_research.news.core.storage_manager import ( InteractionType, ) assert InteractionType.VIEW.value == "view" assert InteractionType.VOTE_UP.value == "vote_up" assert InteractionType.VOTE_DOWN.value == "vote_down" assert InteractionType.RESEARCH.value == "research" assert InteractionType.SHARE.value == "share" class TestStorageManagerProperties: """Tests for StorageManager property accessors.""" @pytest.fixture def storage_manager(self): """Create a StorageManager instance for testing.""" with patch( "local_deep_research.news.core.storage_manager.get_relevance_service" ) as mock_relevance: mock_relevance.return_value = Mock() from local_deep_research.news.core.storage_manager import ( StorageManager, ) return StorageManager() def test_cards_property_with_session(self, storage_manager): """Test cards property when session is available.""" mock_session = Mock() with patch.object( storage_manager, "_get_current_session", return_value=mock_session ): with patch( "local_deep_research.news.core.storage_manager.SQLCardStorage" ) as mock_storage: mock_storage.return_value = Mock() _ = storage_manager.cards mock_storage.assert_called_once_with(mock_session) def test_cards_property_without_session_raises(self, storage_manager): """Test cards property raises when no session available.""" with patch.object( storage_manager, "_get_current_session", return_value=None ): with pytest.raises(RuntimeError) as exc_info: _ = storage_manager.cards assert "No database session" in str(exc_info.value) def test_ratings_property_with_session(self, storage_manager): """Test ratings property when session is available.""" mock_session = Mock() with patch.object( storage_manager, "_get_current_session", return_value=mock_session ): with patch( "local_deep_research.news.core.storage_manager.SQLRatingStorage" ) as mock_storage: mock_storage.return_value = Mock() _ = storage_manager.ratings mock_storage.assert_called_once_with(mock_session) def test_preferences_property_with_session(self, storage_manager): """Test preferences property when session is available.""" mock_session = Mock() with patch.object( storage_manager, "_get_current_session", return_value=mock_session ): with patch( "local_deep_research.news.core.storage_manager.SQLPreferenceStorage" ) as mock_storage: mock_storage.return_value = Mock() _ = storage_manager.preferences mock_storage.assert_called_once_with(mock_session) class TestGetUserFeed: """Tests for get_user_feed method.""" @pytest.fixture def storage_manager(self): """Create a StorageManager instance for testing.""" with patch( "local_deep_research.news.core.storage_manager.get_relevance_service" ) as mock_relevance: mock_relevance.return_value = Mock() from local_deep_research.news.core.storage_manager import ( StorageManager, ) return StorageManager() def test_get_user_feed_success(self, storage_manager): """Test successful user feed retrieval.""" mock_prefs_storage = Mock() mock_prefs_storage.get.return_value = {"liked_categories": ["Tech"]} mock_cards_storage = Mock() mock_cards_storage.list.return_value = [ {"id": "card1"}, {"id": "card2"}, ] mock_card1 = Mock() mock_card2 = Mock() with patch.object( storage_manager, "_get_current_session", return_value=Mock() ): with patch( "local_deep_research.news.core.storage_manager.SQLPreferenceStorage", return_value=mock_prefs_storage, ): with patch( "local_deep_research.news.core.storage_manager.SQLCardStorage", return_value=mock_cards_storage, ): with patch( "local_deep_research.news.core.storage_manager.CardFactory" ) as mock_factory: mock_factory.load_card.side_effect = [ mock_card1, mock_card2, ] storage_manager.relevance_service.personalize_feed.return_value = [ mock_card1, mock_card2, ] result = storage_manager.get_user_feed( "user1", limit=10 ) assert len(result) == 2 def test_get_user_feed_without_preferences(self, storage_manager): """Test user feed when user has no preferences.""" mock_prefs_storage = Mock() mock_prefs_storage.get.return_value = None mock_cards_storage = Mock() mock_cards_storage.list.return_value = [{"id": "card1"}] mock_card = Mock() with patch.object( storage_manager, "_get_current_session", return_value=Mock() ): with patch( "local_deep_research.news.core.storage_manager.SQLPreferenceStorage", return_value=mock_prefs_storage, ): with patch( "local_deep_research.news.core.storage_manager.SQLCardStorage", return_value=mock_cards_storage, ): with patch( "local_deep_research.news.core.storage_manager.CardFactory" ) as mock_factory: mock_factory.load_card.return_value = mock_card storage_manager.relevance_service.personalize_feed.return_value = [ mock_card ] storage_manager.get_user_feed("user1") # Should use user_id filter only mock_cards_storage.list.assert_called_once() call_args = mock_cards_storage.list.call_args assert call_args[1]["filters"]["user_id"] == "user1" def test_get_user_feed_with_card_types(self, storage_manager): """Test user feed filtered by card types.""" mock_prefs_storage = Mock() mock_prefs_storage.get.return_value = None mock_cards_storage = Mock() mock_cards_storage.list.return_value = [] with patch.object( storage_manager, "_get_current_session", return_value=Mock() ): with patch( "local_deep_research.news.core.storage_manager.SQLPreferenceStorage", return_value=mock_prefs_storage, ): with patch( "local_deep_research.news.core.storage_manager.SQLCardStorage", return_value=mock_cards_storage, ): storage_manager.relevance_service.personalize_feed.return_value = [] storage_manager.get_user_feed( "user1", card_types=["news", "alert"] ) call_args = mock_cards_storage.list.call_args assert call_args[1]["filters"]["card_type"] == [ "news", "alert", ] def test_get_user_feed_handles_error(self, storage_manager): """Test that errors are handled gracefully.""" with patch.object( storage_manager, "_get_current_session", return_value=Mock() ): with patch( "local_deep_research.news.core.storage_manager.SQLPreferenceStorage" ) as mock_prefs: mock_prefs.return_value.get.side_effect = Exception("DB Error") result = storage_manager.get_user_feed("user1") assert result == [] class TestGetTrendingNews: """Tests for get_trending_news method.""" @pytest.fixture def storage_manager(self): """Create a StorageManager instance for testing.""" with patch( "local_deep_research.news.core.storage_manager.get_relevance_service" ) as mock_relevance: mock_relevance.return_value = Mock() from local_deep_research.news.core.storage_manager import ( StorageManager, ) return StorageManager() def test_get_trending_news_success(self, storage_manager): """Test successful trending news retrieval.""" mock_cards = [Mock(), Mock()] with patch( "local_deep_research.news.core.storage_manager.CardFactory" ) as mock_factory: mock_factory.get_recent_cards.return_value = mock_cards storage_manager.relevance_service.filter_trending.return_value = ( mock_cards ) result = storage_manager.get_trending_news(hours=24, limit=10) mock_factory.get_recent_cards.assert_called_once_with( hours=24, card_types=["news"], limit=20 ) storage_manager.relevance_service.filter_trending.assert_called_once() assert len(result) == 2 def test_get_trending_news_handles_error(self, storage_manager): """Test that errors are handled gracefully.""" with patch( "local_deep_research.news.core.storage_manager.CardFactory" ) as mock_factory: mock_factory.get_recent_cards.side_effect = Exception("Error") result = storage_manager.get_trending_news() assert result == [] class TestRecordInteraction: """Tests for record_interaction method.""" @pytest.fixture def storage_manager(self): """Create a StorageManager instance for testing.""" with patch( "local_deep_research.news.core.storage_manager.get_relevance_service" ) as mock_relevance: mock_relevance.return_value = Mock() from local_deep_research.news.core.storage_manager import ( StorageManager, ) return StorageManager() def test_record_view_interaction(self, storage_manager): """Test recording a view interaction.""" from local_deep_research.news.core.storage_manager import ( InteractionType, ) mock_card = Mock() mock_card.interaction = {"views": 5} with patch( "local_deep_research.news.core.storage_manager.CardFactory" ) as mock_factory: mock_factory.load_card.return_value = mock_card mock_factory.update_card.return_value = True result = storage_manager.record_interaction( "user1", "card1", InteractionType.VIEW ) assert result is True assert mock_card.interaction["viewed"] is True assert mock_card.interaction["views"] == 6 def test_record_vote_up_interaction(self, storage_manager): """Test recording a vote up interaction.""" from local_deep_research.news.core.storage_manager import ( InteractionType, ) mock_card = Mock() mock_card.interaction = {"votes_up": 10} mock_ratings = Mock() with patch.object( storage_manager, "_get_current_session", return_value=Mock() ): with patch( "local_deep_research.news.core.storage_manager.SQLRatingStorage", return_value=mock_ratings, ): with patch( "local_deep_research.news.core.storage_manager.CardFactory" ) as mock_factory: mock_factory.load_card.return_value = mock_card mock_factory.update_card.return_value = True result = storage_manager.record_interaction( "user1", "card1", InteractionType.VOTE_UP ) assert result is True assert mock_card.interaction["voted"] == "up" assert mock_card.interaction["votes_up"] == 11 mock_ratings.save.assert_called_once() def test_record_vote_down_interaction(self, storage_manager): """Test recording a vote down interaction.""" from local_deep_research.news.core.storage_manager import ( InteractionType, ) mock_card = Mock() mock_card.interaction = {"votes_down": 3} mock_ratings = Mock() with patch.object( storage_manager, "_get_current_session", return_value=Mock() ): with patch( "local_deep_research.news.core.storage_manager.SQLRatingStorage", return_value=mock_ratings, ): with patch( "local_deep_research.news.core.storage_manager.CardFactory" ) as mock_factory: mock_factory.load_card.return_value = mock_card mock_factory.update_card.return_value = True result = storage_manager.record_interaction( "user1", "card1", InteractionType.VOTE_DOWN ) assert result is True assert mock_card.interaction["voted"] == "down" assert mock_card.interaction["votes_down"] == 4 def test_record_research_interaction(self, storage_manager): """Test recording a research interaction.""" from local_deep_research.news.core.storage_manager import ( InteractionType, ) mock_card = Mock() mock_card.interaction = {"research_count": 2} with patch( "local_deep_research.news.core.storage_manager.CardFactory" ) as mock_factory: mock_factory.load_card.return_value = mock_card mock_factory.update_card.return_value = True result = storage_manager.record_interaction( "user1", "card1", InteractionType.RESEARCH ) assert result is True assert mock_card.interaction["researched"] is True assert mock_card.interaction["research_count"] == 3 def test_record_interaction_with_metadata(self, storage_manager): """Test recording interaction with metadata.""" from local_deep_research.news.core.storage_manager import ( InteractionType, ) mock_card = Mock() mock_card.interaction = {"views": 0} with patch( "local_deep_research.news.core.storage_manager.CardFactory" ) as mock_factory: mock_factory.load_card.return_value = mock_card mock_factory.update_card.return_value = True result = storage_manager.record_interaction( "user1", "card1", InteractionType.VIEW, metadata={"source": "feed"}, ) assert result is True assert "InteractionType.VIEW_metadata" in mock_card.interaction def test_record_interaction_card_not_found(self, storage_manager): """Test recording interaction when card not found.""" from local_deep_research.news.core.storage_manager import ( InteractionType, ) with patch( "local_deep_research.news.core.storage_manager.CardFactory" ) as mock_factory: mock_factory.load_card.return_value = None result = storage_manager.record_interaction( "user1", "nonexistent", InteractionType.VIEW ) assert result is False def test_record_interaction_handles_error(self, storage_manager): """Test that errors are handled gracefully.""" from local_deep_research.news.core.storage_manager import ( InteractionType, ) with patch( "local_deep_research.news.core.storage_manager.CardFactory" ) as mock_factory: mock_factory.load_card.side_effect = Exception("Error") result = storage_manager.record_interaction( "user1", "card1", InteractionType.VIEW ) assert result is False class TestCardOperations: """Tests for card-related operations.""" @pytest.fixture def storage_manager(self): """Create a StorageManager instance for testing.""" with patch( "local_deep_research.news.core.storage_manager.get_relevance_service" ) as mock_relevance: mock_relevance.return_value = Mock() from local_deep_research.news.core.storage_manager import ( StorageManager, ) return StorageManager() def test_get_card_success(self, storage_manager): """Test successful card retrieval.""" mock_card = Mock() with patch( "local_deep_research.news.core.storage_manager.CardFactory" ) as mock_factory: mock_factory.load_card.return_value = mock_card result = storage_manager.get_card("card1") assert result == mock_card mock_factory.load_card.assert_called_once_with("card1") def test_get_card_not_found(self, storage_manager): """Test card retrieval when not found.""" with patch( "local_deep_research.news.core.storage_manager.CardFactory" ) as mock_factory: mock_factory.load_card.return_value = None result = storage_manager.get_card("nonexistent") assert result is None def test_get_card_handles_error(self, storage_manager): """Test that errors are handled gracefully.""" with patch( "local_deep_research.news.core.storage_manager.CardFactory" ) as mock_factory: mock_factory.load_card.side_effect = Exception("Error") result = storage_manager.get_card("card1") assert result is None def test_update_card_success(self, storage_manager): """Test successful card update.""" mock_card = Mock() with patch( "local_deep_research.news.core.storage_manager.CardFactory" ) as mock_factory: mock_factory.update_card.return_value = True result = storage_manager.update_card(mock_card) assert result is True mock_factory.update_card.assert_called_once_with(mock_card) def test_update_card_handles_error(self, storage_manager): """Test that errors are handled gracefully.""" mock_card = Mock() with patch( "local_deep_research.news.core.storage_manager.CardFactory" ) as mock_factory: mock_factory.update_card.side_effect = Exception("Error") result = storage_manager.update_card(mock_card) assert result is False def test_get_card_interactions_success(self, storage_manager): """Test successful interaction retrieval.""" mock_ratings = [ {"user_id": "user1", "value": 1, "created_at": "2024-01-01"}, {"user_id": "user2", "value": -1, "created_at": "2024-01-02"}, ] mock_storage = Mock() mock_storage.list.return_value = mock_ratings with patch.object( storage_manager, "_get_current_session", return_value=Mock() ): with patch( "local_deep_research.news.core.storage_manager.SQLRatingStorage", return_value=mock_storage, ): result = storage_manager.get_card_interactions("card1") assert len(result) == 2 assert result[0]["interaction_type"] == "vote" assert result[0]["interaction_data"]["vote"] == "up" assert result[1]["interaction_data"]["vote"] == "down" class TestCleanupOldData: """Tests for cleanup_old_data method.""" @pytest.fixture def storage_manager(self): """Create a StorageManager instance for testing.""" with patch( "local_deep_research.news.core.storage_manager.get_relevance_service" ) as mock_relevance: mock_relevance.return_value = Mock() from local_deep_research.news.core.storage_manager import ( StorageManager, ) return StorageManager() def test_cleanup_old_data_success(self, storage_manager): """Test successful data cleanup.""" mock_old_cards = [{"id": "card1"}, {"id": "card2"}] mock_old_ratings = [{"id": "rating1"}] mock_card_storage = Mock() mock_card_storage.list.return_value = mock_old_cards mock_card_storage.delete.return_value = True mock_rating_storage = Mock() mock_rating_storage.list.return_value = mock_old_ratings with patch.object( storage_manager, "_get_current_session", return_value=Mock() ): with patch( "local_deep_research.news.core.storage_manager.SQLCardStorage", return_value=mock_card_storage, ): with patch( "local_deep_research.news.core.storage_manager.SQLRatingStorage", return_value=mock_rating_storage, ): result = storage_manager.cleanup_old_data(days=30) assert result["cards"] == 2 assert result["ratings"] == 1 def test_cleanup_old_data_handles_error(self, storage_manager): """Test that errors are handled gracefully.""" with patch.object( storage_manager, "_get_current_session", return_value=Mock() ): with patch( "local_deep_research.news.core.storage_manager.SQLCardStorage" ) as mock_storage: mock_storage.return_value.list.side_effect = Exception("Error") result = storage_manager.cleanup_old_data() assert result == {} class TestGetCurrentSession: """Tests for _get_current_session method.""" def test_get_session_from_flask_context(self): """Test getting session from Flask g context (via lazy helper).""" mock_session = Mock() with patch( "local_deep_research.news.core.storage_manager.get_relevance_service" ) as mock_relevance: mock_relevance.return_value = Mock() with patch( "local_deep_research.news.core.storage_manager.has_app_context", return_value=True, ): with patch( "local_deep_research.database.session_context.get_g_db_session", return_value=mock_session, ): from local_deep_research.news.core.storage_manager import ( StorageManager, ) manager = StorageManager() result = manager._get_current_session() assert result == mock_session def test_get_session_no_app_context(self): """Test getting session when no Flask context.""" with patch( "local_deep_research.news.core.storage_manager.get_relevance_service" ) as mock_relevance: mock_relevance.return_value = Mock() with patch( "local_deep_research.news.core.storage_manager.has_app_context", return_value=False, ): from local_deep_research.news.core.storage_manager import ( StorageManager, ) manager = StorageManager() result = manager._get_current_session() assert result is None