Files
mcp-garmin/tests/test_server.py

300 lines
11 KiB
Python
Raw Normal View History

import json
import os
import queue
import threading
from datetime import date
from unittest.mock import MagicMock, patch
import pytest
from garminconnect import GarminConnectAuthenticationError
import server
@pytest.fixture(autouse=True)
def reset_state():
original_state = server._auth_state
original_client = server._client
for q in (server._mfa_input_queue, server._login_result_queue):
while not q.empty():
try:
q.get_nowait()
except queue.Empty:
break
yield
server._auth_state = original_state
server._client = original_client
for q in (server._mfa_input_queue, server._login_result_queue):
while not q.empty():
try:
q.get_nowait()
except queue.Empty:
break
def test_check_auth_unauthenticated():
server._auth_state = "unauthenticated"
assert server._check_auth() == "Not authenticated. Ask Claude to call authenticate() first."
def test_check_auth_authenticated():
server._auth_state = "authenticated"
assert server._check_auth() is None
def test_authenticate_success():
env = {"GARMIN_EMAIL": "test@example.com", "GARMIN_PASSWORD": "secret"}
with patch.dict("os.environ", env):
with patch("server.Garmin") as mock_garmin_cls:
mock_garmin_cls.return_value = MagicMock()
# mock login() returns immediately, thread puts success in queue
result = server.authenticate()
assert result == "Authenticated successfully."
assert server._auth_state == "authenticated"
def test_authenticate_mfa_required():
env = {"GARMIN_EMAIL": "test@example.com", "GARMIN_PASSWORD": "secret"}
with patch.dict("os.environ", env):
with patch("server.Garmin") as mock_garmin_cls:
mock_garmin_cls.return_value = MagicMock()
with patch.object(server._login_result_queue, "get", side_effect=queue.Empty):
result = server.authenticate()
assert "MFA required" in result
assert server._auth_state == "mfa_pending"
def test_complete_mfa_success():
server._auth_state = "mfa_pending"
server._login_result_queue.put(("success", None))
result = server.complete_mfa("123456")
assert result == "MFA accepted. Authenticated successfully."
assert server._auth_state == "authenticated"
assert server._mfa_input_queue.get_nowait() == "123456"
def test_complete_mfa_not_in_progress():
server._auth_state = "unauthenticated"
result = server.complete_mfa("123456")
assert result == "No MFA in progress. Call authenticate() first."
def test_authenticate_missing_credentials():
with patch.dict("os.environ", {}, clear=False):
# Ensure the env vars are absent
os.environ.pop("GARMIN_EMAIL", None)
os.environ.pop("GARMIN_PASSWORD", None)
result = server.authenticate()
assert result == "GARMIN_EMAIL and GARMIN_PASSWORD environment variables are required."
def test_get_activities_unauthenticated():
server._auth_state = "unauthenticated"
assert "Not authenticated" in server.get_activities()
def test_get_activities_success():
server._auth_state = "authenticated"
server._client = MagicMock()
server._client.get_activities_by_date.return_value = [
{"activityId": "111", "activityType": {"typeKey": "running"}, "distance": 5000.0},
{"activityId": "222", "activityType": {"typeKey": "cycling"}, "distance": 20000.0},
]
result = server.get_activities("2026-05-01", "2026-05-17", 20)
data = json.loads(result)
assert len(data) == 2
assert data[0]["activityId"] == "111"
server._client.get_activities_by_date.assert_called_once_with("2026-05-01", "2026-05-17")
def test_get_activities_limit():
server._auth_state = "authenticated"
server._client = MagicMock()
server._client.get_activities_by_date.return_value = [{"activityId": str(i)} for i in range(10)]
data = json.loads(server.get_activities("2026-05-01", "2026-05-17", 3))
assert len(data) == 3
def test_get_activities_defaults_to_today():
server._auth_state = "authenticated"
server._client = MagicMock()
server._client.get_activities_by_date.return_value = []
server.get_activities()
today = date.today().isoformat()
server._client.get_activities_by_date.assert_called_once_with(today, today)
def test_get_activities_auth_error():
server._auth_state = "authenticated"
server._client = MagicMock()
server._client.get_activities_by_date.side_effect = GarminConnectAuthenticationError("auth")
assert "Authentication error" in server.get_activities()
def test_get_activity_details_success():
server._auth_state = "authenticated"
server._client = MagicMock()
server._client.get_activity_details.return_value = {"activityId": "111", "laps": []}
data = json.loads(server.get_activity_details("111"))
assert data["activityId"] == "111"
server._client.get_activity_details.assert_called_once_with("111")
def test_get_activity_details_error():
server._auth_state = "authenticated"
server._client = MagicMock()
server._client.get_activity_details.side_effect = Exception("not found")
assert "Error fetching activity details" in server.get_activity_details("999")
def test_get_sleep_success():
server._auth_state = "authenticated"
server._client = MagicMock()
server._client.get_sleep_data.return_value = {
"dailySleepDTO": {"sleepScores": {"overall": {"value": 85}}}
}
data = json.loads(server.get_sleep("2026-05-17"))
assert data["dailySleepDTO"]["sleepScores"]["overall"]["value"] == 85
server._client.get_sleep_data.assert_called_once_with("2026-05-17")
def test_get_sleep_no_data():
server._auth_state = "authenticated"
server._client = MagicMock()
server._client.get_sleep_data.return_value = None
assert server.get_sleep("2026-05-17") == "No sleep data found for 2026-05-17"
def test_get_heart_rate_success():
server._auth_state = "authenticated"
server._client = MagicMock()
server._client.get_heart_rates.return_value = {"restingHeartRate": 52}
data = json.loads(server.get_heart_rate("2026-05-17"))
assert data["restingHeartRate"] == 52
server._client.get_heart_rates.assert_called_once_with("2026-05-17")
def test_get_stress_success():
server._auth_state = "authenticated"
server._client = MagicMock()
server._client.get_stress_data.return_value = {"stressValuesArray": [[1000, 35]]}
data = json.loads(server.get_stress("2026-05-17"))
assert "stressValuesArray" in data
def test_get_body_battery_success():
server._auth_state = "authenticated"
server._client = MagicMock()
server._client.get_body_battery.return_value = [{"bodyBatteryStatList": []}]
data = json.loads(server.get_body_battery("2026-05-17"))
assert isinstance(data, list)
server._client.get_body_battery.assert_called_once_with("2026-05-17", "2026-05-17")
def test_get_hrv_success():
server._auth_state = "authenticated"
server._client = MagicMock()
server._client.get_hrv_data.return_value = {"hrvSummary": {"weeklyAvg": 45}}
data = json.loads(server.get_hrv("2026-05-17"))
assert data["hrvSummary"]["weeklyAvg"] == 45
def test_get_spo2_success():
server._auth_state = "authenticated"
server._client = MagicMock()
server._client.get_spo2_data.return_value = {"spO2HourlyAverages": []}
data = json.loads(server.get_spo2("2026-05-17"))
assert "spO2HourlyAverages" in data
def test_health_tools_unauthenticated():
server._auth_state = "unauthenticated"
for fn in (server.get_sleep, server.get_heart_rate, server.get_stress,
server.get_body_battery, server.get_hrv, server.get_spo2):
assert "Not authenticated" in fn("2026-05-17")
def test_get_steps_success():
server._auth_state = "authenticated"
server._client = MagicMock()
server._client.get_steps_data.return_value = [{"steps": 8342, "primaryActivityLevel": "active"}]
data = json.loads(server.get_steps("2026-05-17"))
assert data[0]["steps"] == 8342
server._client.get_steps_data.assert_called_once_with("2026-05-17")
def test_get_steps_no_data():
server._auth_state = "authenticated"
server._client = MagicMock()
server._client.get_steps_data.return_value = []
assert server.get_steps("2026-05-17") == "No steps data found for 2026-05-17"
def test_get_daily_stats_success():
server._auth_state = "authenticated"
server._client = MagicMock()
server._client.get_stats.return_value = {
"totalSteps": 9231,
"totalKilocalories": 2100.0,
"floorsAscended": 5,
}
data = json.loads(server.get_daily_stats("2026-05-17"))
assert data["totalSteps"] == 9231
server._client.get_stats.assert_called_once_with("2026-05-17")
def test_steps_and_stats_unauthenticated():
server._auth_state = "unauthenticated"
for fn in (server.get_steps, server.get_daily_stats):
assert "Not authenticated" in fn("2026-05-17")
def test_get_devices_success():
server._auth_state = "authenticated"
server._client = MagicMock()
server._client.get_devices.return_value = [
{"deviceId": "abc123", "productDisplayName": "Forerunner 965", "softwareVersion": "21.20"}
]
data = json.loads(server.get_devices())
assert data[0]["productDisplayName"] == "Forerunner 965"
server._client.get_devices.assert_called_once()
def test_get_devices_empty():
server._auth_state = "authenticated"
server._client = MagicMock()
server._client.get_devices.return_value = []
assert server.get_devices() == "No devices found."
def test_get_gear_success():
server._auth_state = "authenticated"
server._client = MagicMock()
server._client.get_user_profile.return_value = {"userId": 12345}
server._client.get_gear.return_value = [
{"gearPk": "shoe1", "customMakeModel": "Nike Pegasus", "totalDistance": 320000.0}
]
data = json.loads(server.get_gear())
assert data[0]["customMakeModel"] == "Nike Pegasus"
server._client.get_user_profile.assert_called_once()
server._client.get_gear.assert_called_once_with("12345")
def test_get_user_profile_success():
server._auth_state = "authenticated"
server._client = MagicMock()
server._client.get_user_profile.return_value = {
"displayName": "john.doe",
"age": 35,
"weight": 75000.0,
}
data = json.loads(server.get_user_profile())
assert data["displayName"] == "john.doe"
server._client.get_user_profile.assert_called_once()
def test_gear_devices_profile_unauthenticated():
server._auth_state = "unauthenticated"
for fn in (server.get_devices, server.get_gear, server.get_user_profile):
assert "Not authenticated" in fn()