This commit is contained in:
2026-06-12 05:48:30 -04:00
parent 9d91ac8ebc
commit 64a5ab4b7f
37 changed files with 4530 additions and 407 deletions

97
tests/unit/test_auth.py Normal file
View File

@@ -0,0 +1,97 @@
"""Tests for `openrun.ingest.auth` — the web-friendly, MFA-resumable login flow.
We stub garth at the `sso.login` / `sso.resume_login` / `save` boundary so the
two-step (password → MFA code) state machine is testable without a real Garmin
account or network.
"""
from __future__ import annotations
from pathlib import Path
import openrun.ingest.auth as auth
class _FakeClient:
username = "athlete"
oauth1_token = None
oauth2_token = None
def _stub_garth(monkeypatch):
client = _FakeClient()
monkeypatch.setattr(auth.garth, "client", client)
monkeypatch.setattr(auth.garth, "save", lambda td: None)
return client
# --- token store helpers ---------------------------------------------------
def test_has_tokens(tmp_path: Path) -> None:
assert auth.has_tokens(tmp_path) is False
(tmp_path / "oauth1_token.json").write_text("{}")
assert auth.has_tokens(tmp_path) is True
def test_resume_no_tokens_is_false(tmp_path: Path, monkeypatch) -> None:
called = {"n": 0}
monkeypatch.setattr(auth.garth, "resume", lambda td: called.__setitem__("n", called["n"] + 1))
assert auth.resume(tmp_path) is False
assert called["n"] == 0 # short-circuits before touching garth
def test_resume_with_tokens(tmp_path: Path, monkeypatch) -> None:
(tmp_path / "oauth1_token.json").write_text("{}")
monkeypatch.setattr(auth.garth, "resume", lambda td: None)
assert auth.resume(tmp_path) is True
def test_resume_swallows_bad_tokens(tmp_path: Path, monkeypatch) -> None:
(tmp_path / "oauth1_token.json").write_text("garbage")
def _boom(td):
raise ValueError("corrupt")
monkeypatch.setattr(auth.garth, "resume", _boom)
assert auth.resume(tmp_path) is False
# --- login state machine ---------------------------------------------------
def test_begin_login_no_mfa(tmp_path: Path, monkeypatch) -> None:
client = _stub_garth(monkeypatch)
monkeypatch.setattr(auth.garth.sso, "login", lambda *a, **k: ("oauth1", "oauth2"))
kind, payload = auth.begin_login("e@x.com", "pw", tmp_path)
assert kind == "ok"
assert payload == "athlete"
assert client.oauth1_token == "oauth1" and client.oauth2_token == "oauth2"
def test_begin_login_needs_mfa(tmp_path: Path, monkeypatch) -> None:
_stub_garth(monkeypatch)
state = {"session": "opaque"}
monkeypatch.setattr(auth.garth.sso, "login", lambda *a, **k: ("needs_mfa", state))
kind, payload = auth.begin_login("e@x.com", "pw", tmp_path)
assert kind == "needs_mfa"
assert payload is state
def test_complete_mfa(tmp_path: Path, monkeypatch) -> None:
client = _stub_garth(monkeypatch)
seen = {}
def _resume(state, code):
seen["state"], seen["code"] = state, code
return ("o1", "o2")
monkeypatch.setattr(auth.garth.sso, "resume_login", _resume)
user = auth.complete_mfa({"s": 1}, " 123456 ", tmp_path)
assert user == "athlete"
assert seen["code"] == "123456" # trimmed
assert client.oauth1_token == "o1" and client.oauth2_token == "o2"