from unittest.mock import MagicMock, patch import pytest from recommendations.engine import RecommendationEngine from recommendations.tools import FitnessTools @pytest.fixture def mock_genai(): with patch("recommendations.engine.genai.Client") as mock_client_class, \ patch("recommendations.engine.types") as mock_types: mock_client_instance = mock_client_class.return_value # Properly mock Part.from_text to return something simple mock_types.Part.from_text.side_effect = lambda x: MagicMock(text=x) yield mock_client_instance, mock_types def test_engine_init(): engine = RecommendationEngine(api_key="test-key") assert engine.api_key == "test-key" assert engine.client is not None def test_engine_no_key(): with patch("os.getenv", return_value=None): engine = RecommendationEngine(api_key="") assert engine.client is None def test_chat_with_data_success(mock_genai): mock_client, mock_types = mock_genai mock_chat = mock_client.chats.create.return_value mock_response = mock_chat.send_message.return_value mock_response.text = "Mock AI Response" engine = RecommendationEngine(api_key="test-key") history = [{"role": "user", "content": "hi"}] result = engine.chat_with_data("hello", history) assert result == "Mock AI Response" mock_client.chats.create.assert_called_once() def test_chat_with_data_no_text(mock_genai): mock_client, mock_types = mock_genai mock_chat = mock_client.chats.create.return_value mock_response = mock_chat.send_message.return_value mock_response.text = None engine = RecommendationEngine(api_key="test-key") result = engine.chat_with_data("hello") assert "analyzed the data but have no specific comment" in result def test_chat_with_data_no_client(): with patch("os.getenv", return_value=None): engine = RecommendationEngine(api_key="") result = engine.chat_with_data("hi") assert "AI unavailable" in result def test_chat_with_data_error(mock_genai): mock_client, mock_types = mock_genai mock_client.chats.create.side_effect = Exception("API Error") engine = RecommendationEngine(api_key="test-key") result = engine.chat_with_data("hi") assert "error analyzing your data" in result def test_get_recommendation(mock_genai): engine = RecommendationEngine(api_key="test-key") with patch.object(engine, 'chat_with_data', return_value="Tip") as mock_chat: result = engine.get_recommendation([], "run") assert result == "Tip" assert mock_chat.called def test_generate_json_success(mock_genai): mock_client, mock_types = mock_genai mock_response = mock_client.models.generate_content.return_value mock_response.parsed = {"workout": 1} engine = RecommendationEngine(api_key="test-key") result = engine.generate_json("create workout") assert result == {"workout": 1} def test_generate_json_text_fallback(mock_genai): mock_client, mock_types = mock_genai mock_response = mock_client.models.generate_content.return_value mock_response.parsed = None # Wrap JSON in Markdown for realistic fallback mock_response.text = '```json\n{"workout": 2}\n```' engine = RecommendationEngine(api_key="test-key") result = engine.generate_json("modify workout", context_json={"id": 0}) assert result == {"workout": 2} def test_generate_json_error(mock_genai): mock_client, mock_types = mock_genai mock_client.models.generate_content.side_effect = Exception("Gen Error") engine = RecommendationEngine(api_key="test-key") with pytest.raises(Exception): engine.generate_json("hi") def test_generate_json_no_client(): with patch("os.getenv", return_value=None): engine = RecommendationEngine(api_key="") result = engine.generate_json("hi") assert "Offline Workout" in result["workoutName"] # --- FitnessTools Tests --- @pytest.fixture def fitness_tools(): with patch("recommendations.tools.GarminSync"), \ patch("recommendations.tools.SettingsManager"): yield FitnessTools(garmin_storage="/tmp") def test_tools_get_recent_activities(fitness_tools): mock_sync = fitness_tools.sync mock_sync.load_local_activities.return_value = [ {"activityName": "Run", "startTimeLocal": "2023-01-01 10:00:00", "distance": 5000, "duration": 1800, "activityType": {"typeKey": "running"}}, {"activityName": "Cycle", "startTimeLocal": "2023-01-02 10:00:00", "distance": 10000, "duration": 3600, "activityType": {"typeKey": "cycling"}} ] result = fitness_tools.get_recent_activities(limit=2) assert "Run" in result assert "Cycle" in result assert "5.0km" in result def test_tools_get_weekly_stats(fitness_tools): mock_sync = fitness_tools.sync mock_sync.get_weekly_stats.return_value = { "labels": ["W1"], "datasets": [{"label": "Running", "data": [5.0]}] } result = fitness_tools.get_weekly_stats() assert "W1" in result assert "Running: 5.0h" in result def test_tools_get_user_profile(fitness_tools): mock_settings = fitness_tools.settings mock_settings.get_context_string.return_value = "Profile context" result = fitness_tools.get_user_profile() assert result == "Profile context"