From 1184e340013366d2a4a124be249837cacb9a31f6 Mon Sep 17 00:00:00 2001 From: Ron Klinkien Date: Fri, 18 Sep 2026 06:31:11 +0000 Subject: [PATCH] fix: get_goals silently returns [] for newer goal types Closes #431. goal-service/goal/goals requires the Sec-Fetch-Site: same-origin fetch-metadata header for newer goal types (custom accumulation goals with a date range, created via the current Connect UI) - without it the endpoint returns [] with a 200, no error to catch. Browsers always send this on a same-origin XHR; this client doesn't send any Sec-Fetch-* headers otherwise. Root cause isolated by @skweeker via curl - removing only that one header from an otherwise-identical captured browser request reproduced the empty response; restoring it fixed it. Co-Authored-By: Claude Sonnet 5 --- garminconnect/__init__.py | 8 +++++++- tests/test_garmin_unit.py | 9 ++++++++- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/garminconnect/__init__.py b/garminconnect/__init__.py index 714d28be..65134b8b 100644 --- a/garminconnect/__init__.py +++ b/garminconnect/__init__.py @@ -2793,6 +2793,12 @@ def get_goals( "limit": str(limit), "sortOrder": "asc", } + # goal-service silently returns [] for newer goal types (custom + # accumulation goals with a date range, created via the current + # Connect UI) unless this fetch-metadata header is present - #431. + # Browsers always send it on a same-origin XHR; this client doesn't + # send any Sec-Fetch-* headers otherwise, so it has to be explicit. + headers = {"Sec-Fetch-Site": "same-origin"} logger.debug("Requesting %s goals", status) for _ in range(MAX_PAGINATED_REQUESTS): @@ -2800,7 +2806,7 @@ def get_goals( logger.debug( "Requesting %s goals %d to %d", status, start, start + limit - 1 ) - goals_json = self.connectapi(url, params=params) + goals_json = self.connectapi(url, params=params, headers=headers) if goals_json: goals.extend(goals_json) start = start + limit diff --git a/tests/test_garmin_unit.py b/tests/test_garmin_unit.py index 54ebd244..aa0a51f7 100644 --- a/tests/test_garmin_unit.py +++ b/tests/test_garmin_unit.py @@ -1853,7 +1853,7 @@ def test_get_goals_paginates_until_empty(self, garmin: garminconnect.Garmin): [], ] - def fake_connectapi(url, params=None): + def fake_connectapi(url, params=None, headers=None): starts.append(params["start"]) return pages.pop(0) @@ -1864,6 +1864,13 @@ def fake_connectapi(url, params=None): assert mock.call_count == 3 assert starts == ["0", "30", "60"] + def test_get_goals_sends_sec_fetch_site_header(self, garmin: garminconnect.Garmin): + """goal-service silently returns [] without this header (#431).""" + with patch.object(garmin, "connectapi", return_value=[]) as mock: + garmin.get_goals() + + assert mock.call_args.kwargs["headers"] == {"Sec-Fetch-Site": "same-origin"} + # --------------------------------------------------------------------------- # _run_request: HTTP status -> exception mapping