feat(sync): 全部历史真的是全部,不再截断在两年
730 天是个凭空写死的上限。账号有七年数据的人选「全部历史」,拿到的是 最近两年,而且没有任何提示说剩下的被丢掉了。 * 全部历史现在一直回溯到账号最早的数据:连续 EMPTY_RUN_STOP(120) 天 完全没有内容就停,所以既不会截断,也不会去问手表存在之前的年份。 MAX_HISTORY_DAYS(3650) 只是兜底,可用环境变量覆盖。 * 已经存过的日期跳过(最近 3 天除外,它们还在写入中)。这让多年的 回填变成可续传的:撞上限流停下来,冷却过后再点一次就从断点继续, 而不是每次都从今天重新爬。 * 选择器补上 3 年 / 5 年。 * 前端把每个范围的实际代价写出来,并说明中断可续。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -646,7 +646,7 @@ class TestBackgroundSync:
|
||||
lambda uid, creds, days=None: seen.setdefault("days", days) or {"status": "syncing"},
|
||||
)
|
||||
client.post("/api/garmin/sync", headers=auth, json={"days": 99999})
|
||||
assert seen["days"] == 730
|
||||
assert seen["days"] == garmin_svc.MAX_HISTORY_DAYS
|
||||
|
||||
|
||||
class TestRateLimiting:
|
||||
@@ -766,11 +766,49 @@ class TestSyncWindow:
|
||||
pulled = {c[1] for c in client.calls if c[0] == "summary"}
|
||||
assert len(pulled) == garmin_svc.DEFAULT_SYNC_DAYS
|
||||
|
||||
def test_full_history_asks_for_the_maximum_window(self, db, user):
|
||||
"""`days=0` is 全部历史, not "unset"."""
|
||||
def test_full_history_asks_for_the_maximum_window(self, db, user, monkeypatch):
|
||||
"""`days=0` is 全部历史, not "unset" — and not a fixed two years."""
|
||||
monkeypatch.setattr(garmin_svc, "MAX_HISTORY_DAYS", 50)
|
||||
client = StubClient()
|
||||
garmin_svc.sync_data(user["id"], CREDS, days=0, client=client)
|
||||
assert len([c for c in client.calls if c[0] == "summary"]) == 730
|
||||
assert len([c for c in client.calls if c[0] == "summary"]) == 50
|
||||
|
||||
def test_days_already_stored_are_not_refetched(self, db, user, seed_health):
|
||||
"""What makes a multi-year backfill resumable: a run that stopped on a
|
||||
429 must continue where it left off, not start over from today."""
|
||||
seed_health([{"date": day(i), "steps": 4000} for i in range(5, 20)])
|
||||
client = StubClient()
|
||||
garmin_svc.sync_data(user["id"], CREDS, days=20, client=client)
|
||||
|
||||
pulled = sorted(c[1] for c in client.calls if c[0] == "summary")
|
||||
assert pulled == sorted(day(i) for i in range(5)), "only the gap"
|
||||
|
||||
def test_the_most_recent_days_are_always_refetched(self, db, user, seed_health):
|
||||
"""Today is still being written to; a stored row for it is not final."""
|
||||
seed_health([{"date": day(i), "steps": 4000} for i in range(0, 10)])
|
||||
client = StubClient()
|
||||
garmin_svc.sync_data(user["id"], CREDS, days=10, client=client)
|
||||
|
||||
pulled = {c[1] for c in client.calls if c[0] == "summary"}
|
||||
assert pulled == {day(i) for i in range(garmin_svc.ALWAYS_REFETCH_DAYS)}
|
||||
|
||||
def test_a_long_backfill_stops_at_the_start_of_the_account(self, db, user,
|
||||
monkeypatch):
|
||||
"""全部历史 must not spend thousands of requests on years that predate
|
||||
the watch."""
|
||||
monkeypatch.setattr(garmin_svc, "EMPTY_RUN_STOP", 10)
|
||||
empty = {"totalSteps": None, "restingHeartRate": None,
|
||||
"averageStressLevel": None, "totalKilocalories": None}
|
||||
client = StubClient(
|
||||
summaries={day(i): empty for i in range(3, 400)},
|
||||
sleeps={day(i): {} for i in range(3, 400)},
|
||||
hrvs={day(i): {} for i in range(3, 400)},
|
||||
)
|
||||
out = garmin_svc.sync_data(user["id"], CREDS, days=365, client=client)
|
||||
|
||||
assert out["recordsSynced"] == 3, "the three days that had data"
|
||||
pulled = len([c for c in client.calls if c[0] == "summary"])
|
||||
assert pulled == 13, f"3 real days + 10 empties, not 365 ({pulled})"
|
||||
|
||||
def test_the_sync_endpoint_passes_a_zero_through(
|
||||
self, client, auth, user, db, monkeypatch
|
||||
@@ -785,7 +823,9 @@ class TestSyncWindow:
|
||||
monkeypatch.setattr(garmin_svc, "start_sync", record)
|
||||
r = client.post("/api/garmin/sync", headers=auth, json={"days": 0})
|
||||
assert r.status_code == 202
|
||||
assert seen["days"] == 730, "全部历史 must not fall back to the default"
|
||||
# 0 travels all the way to the service, which walks back to the real
|
||||
# end of the account rather than to a fixed ceiling.
|
||||
assert seen["days"] == 0, "全部历史 must not fall back to the default"
|
||||
|
||||
def test_a_429_mid_run_stops_the_sync(self, db, user):
|
||||
"""Filing a 429 as one more skipped day meant a 730-day backfill kept
|
||||
|
||||
Reference in New Issue
Block a user