saving
This commit is contained in:
135
tests/unit/test_fit_download.py
Normal file
135
tests/unit/test_fit_download.py
Normal file
@@ -0,0 +1,135 @@
|
||||
"""Path B per-second: pulling original FITs from the API instead of a website export.
|
||||
|
||||
`download_fit` / `backfill_fits` are mocked at the `garth.download` boundary
|
||||
(network-dependent, upstream-deprecated — see ROADMAP test conventions). The FIT
|
||||
extractor is pure and tested directly.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import io
|
||||
import zipfile
|
||||
|
||||
import pytest
|
||||
|
||||
from openrun.ingest import garmin_api as g
|
||||
|
||||
# A FIT header carries the literal b".FIT" at bytes 8..12; payload after is opaque here.
|
||||
FIT_BYTES = b"\x0e\x10\x00\x00\x20\x00\x00\x00.FITdata-goes-here"
|
||||
|
||||
|
||||
def _zip(*members: tuple[str, bytes]) -> bytes:
|
||||
buf = io.BytesIO()
|
||||
with zipfile.ZipFile(buf, "w") as zf:
|
||||
for name, data in members:
|
||||
zf.writestr(name, data)
|
||||
return buf.getvalue()
|
||||
|
||||
|
||||
# --- _extract_fit_bytes ----------------------------------------------------
|
||||
|
||||
def test_extract_from_zip():
|
||||
assert g._extract_fit_bytes(_zip(("9.fit", FIT_BYTES))) == FIT_BYTES
|
||||
|
||||
|
||||
def test_extract_from_zip_extensionless_member_picks_largest():
|
||||
blob = _zip(("small", b"x"), ("big", FIT_BYTES))
|
||||
assert g._extract_fit_bytes(blob) == FIT_BYTES
|
||||
|
||||
|
||||
def test_extract_from_bare_fit():
|
||||
assert g._extract_fit_bytes(FIT_BYTES) == FIT_BYTES
|
||||
|
||||
|
||||
@pytest.mark.parametrize("blob", [None, b"", b"not a fit at all"])
|
||||
def test_extract_rejects_non_fit(blob):
|
||||
assert g._extract_fit_bytes(blob) is None
|
||||
|
||||
|
||||
# --- download_fit ----------------------------------------------------------
|
||||
|
||||
def _seed_activity(conn, aid: int, atype: str = "running") -> None:
|
||||
conn.execute(
|
||||
"INSERT INTO activities (activity_id, activity_type, start_time_gmt, raw, fetched_at) "
|
||||
"VALUES (?, ?, ?, ?, datetime('now'))",
|
||||
(aid, atype, "2026-05-01T10:00:00", "{}"),
|
||||
)
|
||||
|
||||
|
||||
def test_download_fit_writes_and_links(tmp_conn, tmp_path, monkeypatch):
|
||||
_seed_activity(tmp_conn, 42)
|
||||
monkeypatch.setattr(g.garth, "download", lambda path: _zip(("42.fit", FIT_BYTES)))
|
||||
|
||||
ok = g.download_fit(tmp_conn, 42, fit_dir=tmp_path)
|
||||
|
||||
assert ok is True
|
||||
dest = tmp_path / "42.fit"
|
||||
assert dest.read_bytes() == FIT_BYTES
|
||||
row = tmp_conn.execute(
|
||||
"SELECT fit_path FROM activity_fit_files WHERE activity_id = 42"
|
||||
).fetchone()
|
||||
assert row is not None and row[0].endswith("42.fit")
|
||||
|
||||
|
||||
def test_download_fit_skips_when_already_linked(tmp_conn, tmp_path, monkeypatch):
|
||||
_seed_activity(tmp_conn, 7)
|
||||
(tmp_path / "7.fit").write_bytes(FIT_BYTES)
|
||||
g.record_link(tmp_conn, 7, tmp_path / "7.fit")
|
||||
|
||||
calls = []
|
||||
monkeypatch.setattr(g.garth, "download", lambda path: calls.append(path) or b"")
|
||||
|
||||
assert g.download_fit(tmp_conn, 7, fit_dir=tmp_path) is True
|
||||
assert calls == [] # no network call when on disk + linked
|
||||
|
||||
|
||||
def test_download_fit_force_redownloads(tmp_conn, tmp_path, monkeypatch):
|
||||
_seed_activity(tmp_conn, 7)
|
||||
(tmp_path / "7.fit").write_bytes(b"stale")
|
||||
g.record_link(tmp_conn, 7, tmp_path / "7.fit")
|
||||
monkeypatch.setattr(g.garth, "download", lambda path: _zip(("7.fit", FIT_BYTES)))
|
||||
|
||||
assert g.download_fit(tmp_conn, 7, fit_dir=tmp_path, force=True) is True
|
||||
assert (tmp_path / "7.fit").read_bytes() == FIT_BYTES
|
||||
|
||||
|
||||
def test_download_fit_returns_false_on_empty_response(tmp_conn, tmp_path, monkeypatch):
|
||||
_seed_activity(tmp_conn, 99)
|
||||
monkeypatch.setattr(g.garth, "download", lambda path: None)
|
||||
|
||||
assert g.download_fit(tmp_conn, 99, fit_dir=tmp_path) is False
|
||||
assert tmp_conn.execute(
|
||||
"SELECT 1 FROM activity_fit_files WHERE activity_id = 99"
|
||||
).fetchone() is None
|
||||
|
||||
|
||||
# --- backfill_fits ---------------------------------------------------------
|
||||
|
||||
def test_backfill_only_targets_unlinked(tmp_conn, tmp_path, monkeypatch):
|
||||
_seed_activity(tmp_conn, 1)
|
||||
_seed_activity(tmp_conn, 2)
|
||||
(tmp_path / "1.fit").write_bytes(FIT_BYTES)
|
||||
g.record_link(tmp_conn, 1, tmp_path / "1.fit") # 1 already has a FIT
|
||||
|
||||
pulled = []
|
||||
monkeypatch.setattr(
|
||||
g.garth, "download",
|
||||
lambda path: pulled.append(path) or _zip(("x.fit", FIT_BYTES)),
|
||||
)
|
||||
|
||||
got = g.backfill_fits(tmp_conn, fit_dir=tmp_path)
|
||||
|
||||
assert got == 1
|
||||
assert pulled == ["/download-service/files/activity/2"]
|
||||
|
||||
|
||||
def test_backfill_respects_type_filter(tmp_conn, tmp_path, monkeypatch):
|
||||
_seed_activity(tmp_conn, 10, atype="running")
|
||||
_seed_activity(tmp_conn, 11, atype="cycling")
|
||||
monkeypatch.setattr(g.garth, "download", lambda path: _zip(("x.fit", FIT_BYTES)))
|
||||
|
||||
got = g.backfill_fits(tmp_conn, fit_dir=tmp_path, activity_type="running")
|
||||
|
||||
assert got == 1
|
||||
assert (tmp_path / "10.fit").exists()
|
||||
assert not (tmp_path / "11.fit").exists()
|
||||
Reference in New Issue
Block a user