143 lines
5.3 KiB
Python
143 lines
5.3 KiB
Python
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"
|