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({})