FitMop/backend/tests/test_garmin_client.py

237 lines
8.9 KiB
Python

from datetime import date
from unittest.mock import MagicMock, patch
import pytest
from garmin.client import GarminClient
@pytest.fixture
def mock_garmin():
with patch("garmin.client.Garmin") as mock:
yield mock
@pytest.fixture
def mock_sso():
with patch("garmin.client.garth_login") as mock_login, \
patch("garmin.client.resume_login") as mock_resume_login:
yield mock_login, mock_resume_login
@pytest.fixture(autouse=True)
def clean_client():
"""Ensure static state is clean."""
GarminClient._temp_client_state = None
yield
GarminClient._temp_client_state = None
def test_client_init():
client = GarminClient(email="test@example.com", password="password")
assert client.email == "test@example.com"
assert client.password == "password"
def test_login_success_force(mock_sso, mock_garmin):
mock_login, _ = mock_sso
mock_login.return_value = (MagicMock(), MagicMock())
client = GarminClient(email="test@example.com", password="password")
with patch("os.path.exists", return_value=False):
assert client.login(force_login=True) == "SUCCESS"
mock_login.assert_called_once()
def test_login_mfa_required(mock_sso):
mock_login, _ = mock_sso
mock_login.return_value = ("needs_mfa", {"some": "state"})
client = GarminClient(email="test@example.com", password="password")
with patch("os.path.exists", return_value=False):
assert client.login(force_login=True) == "MFA_REQUIRED"
assert GarminClient._temp_client_state == {"some": "state"}
def test_login_mfa_complete(mock_sso, mock_garmin):
_, mock_resume_login = mock_sso
mock_client = MagicMock()
mock_client.oauth1_token = MagicMock()
state = {"some": "state", "client": mock_client}
GarminClient._temp_client_state = state
mock_resume_login.return_value = (MagicMock(), MagicMock())
client = GarminClient(email="test@example.com", password="password")
assert client.login(mfa_code="123456") == "SUCCESS"
mock_resume_login.assert_called_with(state, "123456")
def test_login_mfa_complete_no_client_in_state(mock_sso, mock_garmin):
_, mock_resume_login = mock_sso
state = {"some": "state"}
GarminClient._temp_client_state = state
mock_resume_login.return_value = (MagicMock(), MagicMock())
client = GarminClient(email="test@example.com", password="password")
with patch("garmin.client.garth") as mock_garth:
assert client.login(mfa_code="123456") == "SUCCESS"
mock_garth.client.configure.assert_called_once()
def test_login_mfa_required_no_creds(mock_garmin, monkeypatch):
monkeypatch.setenv("GARMIN_EMAIL", "")
monkeypatch.setenv("GARMIN_PASSWORD", "")
client = GarminClient(email="", password="")
GarminClient._temp_client_state = {"some": "state"}
with patch("os.path.exists", return_value=False):
assert client.login() == "MFA_REQUIRED"
def test_login_resume_success(mock_garmin):
client = GarminClient(email="test@example.com", password="password")
inst = mock_garmin.return_value
with patch("os.path.exists", return_value=True), \
patch("os.path.getsize", return_value=100):
assert client.login() == "SUCCESS"
inst.login.assert_called_with(tokenstore=client.token_store)
def test_login_resume_fail_falls_back(mock_garmin, mock_sso):
mock_login, _ = mock_sso
mock_login.return_value = (MagicMock(), MagicMock())
inst = mock_garmin.return_value
inst.login.side_effect = Exception("Resume fail")
client = GarminClient(email="test", password="test")
# Step 3 will check if creds exist. If they do, it goes to login.
# But resume_fail_falls_back test expects FAILURE if not force_login.
with patch("os.path.exists", return_value=True), \
patch("os.path.getsize", return_value=100), \
patch("os.remove"):
assert client.login() == "FAILURE"
def test_login_resume_fail_force_retries(mock_garmin, mock_sso):
mock_login, _ = mock_sso
mock_login.return_value = (MagicMock(), MagicMock())
inst = mock_garmin.return_value
# First call to inst.login (resume) fails, second call (new login) succeeds
inst.login.side_effect = [Exception("Resume fail"), None]
client = GarminClient(email="test", password="test")
with patch("os.path.exists", return_value=True), \
patch("os.path.getsize", return_value=100), \
patch("os.remove"):
assert client.login(force_login=True) == "SUCCESS"
assert mock_login.called
def test_login_empty_token_cleanup(mock_garmin):
client = GarminClient()
with patch("os.path.exists", return_value=True), \
patch("os.path.getsize", return_value=0), \
patch("os.remove") as mock_remove:
assert client.login() == "FAILURE"
assert mock_remove.called
def test_login_json_error_cleanup(mock_garmin):
client = GarminClient()
inst = mock_garmin.return_value
inst.login.side_effect = Exception("Expecting value: line 1 column 1")
with patch("os.path.exists", return_value=True), \
patch("os.path.getsize", return_value=100), \
patch("os.remove") as mock_remove:
assert client.login() == "FAILURE"
assert mock_remove.called
def test_login_missing_creds(mock_garmin, monkeypatch):
monkeypatch.setenv("GARMIN_EMAIL", "")
monkeypatch.setenv("GARMIN_PASSWORD", "")
client = GarminClient(email="", password="")
with patch("os.path.exists", return_value=False):
assert client.login() == "FAILURE"
def test_get_activities_error(mock_garmin):
mock_instance = mock_garmin.return_value
mock_instance.get_activities_by_date.side_effect = Exception("API Error")
client = GarminClient()
client.client = mock_instance
assert client.get_activities(date(2023, 1, 1), date(2023, 1, 2)) == []
def test_get_stats_success(mock_garmin):
mock_instance = mock_garmin.return_value
mock_instance.get_stats.return_value = {"steps": 1000}
client = GarminClient()
client.client = mock_instance
assert client.get_stats(date(2023, 1, 1)) == {"steps": 1000}
def test_get_stats_error(mock_garmin):
mock_instance = mock_garmin.return_value
mock_instance.get_stats.side_effect = Exception("Err")
client = GarminClient()
client.client = mock_instance
assert client.get_stats(date(2023, 1, 1)) == {}
def test_get_user_summary_success(mock_garmin):
mock_instance = mock_garmin.return_value
mock_instance.get_user_summary.return_value = {"calories": 2000}
client = GarminClient()
client.client = mock_instance
assert client.get_user_summary(date(2023, 1, 1)) == {"calories": 2000}
def test_get_user_summary_error(mock_garmin):
mock_instance = mock_garmin.return_value
mock_instance.get_user_summary.side_effect = Exception("Err")
client = GarminClient()
client.client = mock_instance
assert client.get_user_summary(date(2023, 1, 1)) == {}
def test_get_workouts_list_success(mock_garmin):
mock_instance = mock_garmin.return_value
mock_instance.get_workouts.return_value = [{"name": "W1"}]
client = GarminClient()
client.client = mock_instance
assert client.get_workouts_list() == [{"name": "W1"}]
def test_get_workouts_list_error(mock_garmin):
mock_instance = mock_garmin.return_value
mock_instance.get_workouts.side_effect = Exception("Err")
client = GarminClient()
client.client = mock_instance
assert client.get_workouts_list() == []
def test_get_workout_detail_success(mock_garmin):
mock_instance = mock_garmin.return_value
mock_instance.get_workout_by_id.return_value = {"id": "1"}
client = GarminClient()
client.client = mock_instance
assert client.get_workout_detail("1") == {"id": "1"}
def test_get_workout_detail_error(mock_garmin):
mock_instance = mock_garmin.return_value
mock_instance.get_workout_by_id.side_effect = Exception("Err")
client = GarminClient()
client.client = mock_instance
assert client.get_workout_detail("1") == {}
def test_upload_workout_success(mock_garmin):
mock_instance = mock_garmin.return_value
client = GarminClient()
client.client = mock_instance
assert client.upload_workout({"json": True}) is True
def test_upload_workout_error(mock_garmin):
mock_instance = mock_garmin.return_value
mock_instance.upload_workout.side_effect = Exception("Err")
client = GarminClient()
client.client = mock_instance
assert client.upload_workout({"json": True}) is False
def test_not_logged_in_errors():
client = GarminClient()
with pytest.raises(RuntimeError):
client.get_activities(date.today(), date.today())
with pytest.raises(RuntimeError):
client.get_stats(date.today())
with pytest.raises(RuntimeError):
client.get_user_summary(date.today())
with pytest.raises(RuntimeError):
client.get_workouts_list()
with pytest.raises(RuntimeError):
client.get_workout_detail("1")
with pytest.raises(RuntimeError):
client.upload_workout({})