127 lines
4.4 KiB
Python
127 lines
4.4 KiB
Python
|
|
import os
|
||
|
|
import queue
|
||
|
|
from unittest.mock import MagicMock, patch
|
||
|
|
|
||
|
|
import pytest
|
||
|
|
|
||
|
|
import wrapper
|
||
|
|
|
||
|
|
|
||
|
|
@pytest.fixture(autouse=True)
|
||
|
|
def reset_state():
|
||
|
|
original_state = wrapper._auth_state
|
||
|
|
original_client = wrapper._client
|
||
|
|
for q in (wrapper._mfa_input_queue, wrapper._login_result_queue):
|
||
|
|
while not q.empty():
|
||
|
|
try:
|
||
|
|
q.get_nowait()
|
||
|
|
except queue.Empty:
|
||
|
|
break
|
||
|
|
yield
|
||
|
|
wrapper._auth_state = original_state
|
||
|
|
wrapper._client = original_client
|
||
|
|
|
||
|
|
|
||
|
|
def test_authenticate_success():
|
||
|
|
env = {"GARMIN_EMAIL": "test@example.com", "GARMIN_PASSWORD": "secret"}
|
||
|
|
with patch.dict("os.environ", env):
|
||
|
|
with patch("wrapper.Garmin") as mock_garmin_cls:
|
||
|
|
mock_garmin_cls.return_value = MagicMock()
|
||
|
|
resp = wrapper.dispatch({"id": 1, "cmd": "authenticate"})
|
||
|
|
assert resp == {"id": 1, "result": {"status": "success", "message": "Authenticated successfully."}}
|
||
|
|
assert wrapper._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("wrapper.Garmin") as mock_garmin_cls:
|
||
|
|
mock_garmin_cls.return_value = MagicMock()
|
||
|
|
with patch.object(wrapper._login_result_queue, "get", side_effect=queue.Empty):
|
||
|
|
resp = wrapper.dispatch({"id": 2, "cmd": "authenticate"})
|
||
|
|
assert resp["result"]["status"] == "mfa_required"
|
||
|
|
assert wrapper._auth_state == "mfa_pending"
|
||
|
|
|
||
|
|
|
||
|
|
def test_authenticate_missing_credentials():
|
||
|
|
with patch.dict("os.environ", {}, clear=False):
|
||
|
|
os.environ.pop("GARMIN_EMAIL", None)
|
||
|
|
os.environ.pop("GARMIN_PASSWORD", None)
|
||
|
|
resp = wrapper.dispatch({"id": 3, "cmd": "authenticate"})
|
||
|
|
assert resp["result"]["status"] == "failed"
|
||
|
|
assert "GARMIN_EMAIL" in resp["result"]["message"]
|
||
|
|
|
||
|
|
|
||
|
|
def test_complete_mfa_success():
|
||
|
|
wrapper._auth_state = "mfa_pending"
|
||
|
|
wrapper._login_result_queue.put(("success", None))
|
||
|
|
resp = wrapper.dispatch({"id": 4, "cmd": "complete_mfa", "params": {"code": "123456"}})
|
||
|
|
assert resp == {
|
||
|
|
"id": 4,
|
||
|
|
"result": {"status": "success", "message": "MFA accepted. Authenticated successfully."},
|
||
|
|
}
|
||
|
|
assert wrapper._auth_state == "authenticated"
|
||
|
|
assert wrapper._mfa_input_queue.get_nowait() == "123456"
|
||
|
|
|
||
|
|
|
||
|
|
def test_complete_mfa_not_in_progress():
|
||
|
|
wrapper._auth_state = "unauthenticated"
|
||
|
|
resp = wrapper.dispatch({"id": 5, "cmd": "complete_mfa", "params": {"code": "123456"}})
|
||
|
|
assert resp["result"]["status"] == "failed"
|
||
|
|
assert "No MFA in progress" in resp["result"]["message"]
|
||
|
|
|
||
|
|
|
||
|
|
def test_call_dispatches_to_named_garminconnect_method():
|
||
|
|
wrapper._auth_state = "authenticated"
|
||
|
|
wrapper._client = MagicMock()
|
||
|
|
wrapper._client.get_activities_by_date.return_value = [{"activityId": "111"}]
|
||
|
|
|
||
|
|
resp = wrapper.dispatch({
|
||
|
|
"id": 6,
|
||
|
|
"cmd": "call",
|
||
|
|
"params": {
|
||
|
|
"method": "get_activities_by_date",
|
||
|
|
"args": {"startdate": "2026-07-01", "enddate": "2026-07-25"},
|
||
|
|
},
|
||
|
|
})
|
||
|
|
|
||
|
|
assert resp == {"id": 6, "result": [{"activityId": "111"}]}
|
||
|
|
wrapper._client.get_activities_by_date.assert_called_once_with(
|
||
|
|
startdate="2026-07-01", enddate="2026-07-25"
|
||
|
|
)
|
||
|
|
|
||
|
|
|
||
|
|
def test_call_unauthenticated_is_error():
|
||
|
|
wrapper._auth_state = "unauthenticated"
|
||
|
|
resp = wrapper.dispatch({
|
||
|
|
"id": 7, "cmd": "call", "params": {"method": "get_activities_by_date", "args": {}},
|
||
|
|
})
|
||
|
|
assert "error" in resp
|
||
|
|
assert "Not authenticated" in resp["error"]
|
||
|
|
|
||
|
|
|
||
|
|
def test_call_unknown_method_is_error():
|
||
|
|
wrapper._auth_state = "authenticated"
|
||
|
|
wrapper._client = MagicMock(spec=["get_activities_by_date"])
|
||
|
|
resp = wrapper.dispatch({
|
||
|
|
"id": 8, "cmd": "call", "params": {"method": "delete_everything", "args": {}},
|
||
|
|
})
|
||
|
|
assert resp["id"] == 8
|
||
|
|
assert "error" in resp
|
||
|
|
|
||
|
|
|
||
|
|
def test_call_propagates_garminconnect_exception_as_error():
|
||
|
|
wrapper._auth_state = "authenticated"
|
||
|
|
wrapper._client = MagicMock()
|
||
|
|
wrapper._client.get_activity_splits.side_effect = Exception("not found")
|
||
|
|
resp = wrapper.dispatch({
|
||
|
|
"id": 9, "cmd": "call", "params": {"method": "get_activity_splits", "args": {"activity_id": "999"}},
|
||
|
|
})
|
||
|
|
assert resp == {"id": 9, "error": "not found"}
|
||
|
|
|
||
|
|
|
||
|
|
def test_dispatch_unknown_cmd_is_error():
|
||
|
|
resp = wrapper.dispatch({"id": 10, "cmd": "not_a_real_cmd"})
|
||
|
|
assert resp["id"] == 10
|
||
|
|
assert "unknown cmd" in resp["error"]
|