Skip to content

Commit a36a9a0

Browse files
Elarwei001claude
andcommitted
test(pineapple): add live Google Drive accessibility tests; refactor resolve
Sweep all 30 catalog resources (segmentation/benchmark/weights) against Google Drive to guarantee the curated data stays downloadable. The new TestPineappleLiveAccess checks headers only (no body download): each file ID must still resolve to a binary attachment whose filename matches the catalog and is not a tiny placeholder; transient Google Drive throttling (HTML quota page) is treated as unavailable, not a failure. Extract _resolve_gdrive_response from _download_from_gdrive so the live tests exercise the same production resolution path, including the large-file virus-scan-warning confirmation. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 5d9db77 commit a36a9a0

2 files changed

Lines changed: 119 additions & 7 deletions

File tree

gget/gget_pineapple.py

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -269,11 +269,15 @@ def _parse_gdrive_form(html_text: str) -> tuple[str | None, dict[str, str]]:
269269
return action, params
270270

271271

272-
def _download_from_gdrive(file_id: str, dest_path: str, verbose: bool = True) -> None:
273-
"""Download a (potentially large) file from Google Drive by file ID."""
274-
session = requests.Session()
275-
session.headers.update({"User-Agent": "Mozilla/5.0 (compatible; gget)"})
276-
272+
def _resolve_gdrive_response(session: requests.Session, file_id: str) -> requests.Response:
273+
"""Resolve a Google Drive file ID to a streaming response for the actual file.
274+
275+
Small files download directly; large files first return an HTML
276+
"can't scan for viruses" warning page whose form must be submitted to
277+
obtain the real file. The returned response is opened with ``stream=True``,
278+
so the body is not fetched until the caller iterates it. Callers that only
279+
need the headers (e.g. accessibility checks) must ``close()`` the response.
280+
"""
277281
response = session.get(
278282
PINEAPPLE_GDRIVE_URL,
279283
params={"id": file_id},
@@ -282,8 +286,6 @@ def _download_from_gdrive(file_id: str, dest_path: str, verbose: bool = True) ->
282286
)
283287
response.raise_for_status()
284288

285-
# Small files download directly; large files first return an HTML
286-
# "can't scan for viruses" warning page that must be confirmed.
287289
content_type = response.headers.get("Content-Type", "")
288290
if "text/html" in content_type:
289291
action, params = _parse_gdrive_form(response.text)
@@ -302,6 +304,16 @@ def _download_from_gdrive(file_id: str, dest_path: str, verbose: bool = True) ->
302304
)
303305
response.raise_for_status()
304306

307+
return response
308+
309+
310+
def _download_from_gdrive(file_id: str, dest_path: str, verbose: bool = True) -> None:
311+
"""Download a (potentially large) file from Google Drive by file ID."""
312+
session = requests.Session()
313+
session.headers.update({"User-Agent": "Mozilla/5.0 (compatible; gget)"})
314+
315+
response = _resolve_gdrive_response(session, file_id)
316+
305317
with open(dest_path, "wb") as fh:
306318
for chunk in response.iter_content(chunk_size=32768):
307319
if chunk:

tests/test_pineapple.py

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from unittest.mock import patch
44

55
import gget.gget_pineapple as gget_pineapple
6+
import requests
67
from gget.gget_pineapple import (
78
_catalog_row,
89
_parse_gdrive_form,
@@ -87,5 +88,104 @@ def test_download_invokes_gdrive(self, mock_dl):
8788
self.assertEqual(df.iloc[0]["name"], "vicar_2021")
8889

8990

91+
class TestPineappleLiveAccess(unittest.TestCase):
92+
"""Live data test: verify EVERY curated Pineapple resource is still
93+
downloadable from Google Drive (issue #161).
94+
95+
This hits Google Drive over the network but never downloads the (multi-GB)
96+
bodies. Each file ID is resolved through the *same* production code path as
97+
a real download (``_resolve_gdrive_response``, including the large-file
98+
virus-scan-warning confirmation), and only the response headers are checked,
99+
so the whole catalog can be swept cheaply.
100+
101+
Purpose: if someone edits the catalog, or upstream repoints/removes a file,
102+
this fails loudly -- the Google-Drive-reported filename must still match the
103+
catalog and the ID must still resolve to a binary download rather than an
104+
error/quota HTML page.
105+
"""
106+
107+
_CATALOGS = {
108+
"segmentation": gget_pineapple._SEGMENTATION,
109+
"benchmark": gget_pineapple._BENCHMARK,
110+
"weights": gget_pineapple._WEIGHTS,
111+
}
112+
113+
# 1 MB floor: catches an ID repointed to a tiny placeholder/error file.
114+
# NOT tied to the catalog's size_gb, which is only approximate (e.g.
115+
# livecell_2021 lists 3.26 GB but the real file is ~1.81 GB).
116+
_MIN_BYTES = 1_000_000
117+
118+
def _assert_resource_headers(self, response, expected, name):
119+
"""Header-only assertions for a resolved resource (no body download)."""
120+
self.assertEqual(response.status_code, 200, f"{name}: unexpected status code")
121+
self.assertIn(
122+
"octet-stream",
123+
response.headers.get("Content-Type", ""),
124+
f"{name}: expected a binary download, got Content-Type "
125+
f"{response.headers.get('Content-Type', '')!r}",
126+
)
127+
disposition = response.headers.get("Content-Disposition", "")
128+
self.assertIn(
129+
f'filename="{expected["filename"]}"',
130+
disposition,
131+
f"{name}: Google Drive filename does not match the catalog "
132+
f"(expected {expected['filename']!r}, Content-Disposition={disposition!r}). "
133+
f"The file ID may have been repointed upstream.",
134+
)
135+
length = response.headers.get("Content-Length")
136+
self.assertIsNotNone(length, f"{name}: response is missing Content-Length")
137+
self.assertGreater(
138+
int(length),
139+
self._MIN_BYTES,
140+
f"{name}: file is implausibly small ({length} bytes) -- possible placeholder",
141+
)
142+
143+
def test_live_all_resources_accessible(self):
144+
session = requests.Session()
145+
session.headers.update({"User-Agent": "Mozilla/5.0 (compatible; gget)"})
146+
147+
total = sum(len(cat) for cat in self._CATALOGS.values())
148+
verified = 0
149+
unavailable = [] # (name, reason) -- transient: throttling / network, not a failure
150+
151+
for category, catalog in self._CATALOGS.items():
152+
for name, info in catalog.items():
153+
expected = _catalog_row(category, name, info)
154+
with self.subTest(category=category, name=name):
155+
try:
156+
response = gget_pineapple._resolve_gdrive_response(
157+
session, expected["google_drive_id"]
158+
)
159+
except requests.RequestException as exc:
160+
unavailable.append((name, f"network error: {exc}"))
161+
continue
162+
try:
163+
# Google Drive serves a transient HTML "download quota
164+
# exceeded" page under load. Treat as unavailable (not a
165+
# failure) and move on; a genuinely missing file would have
166+
# raised a 404/410 in _resolve_gdrive_response above.
167+
if "text/html" in response.headers.get("Content-Type", ""):
168+
unavailable.append((name, "Google Drive HTML page (quota throttling?)"))
169+
continue
170+
self._assert_resource_headers(response, expected, name)
171+
verified += 1
172+
finally:
173+
response.close()
174+
175+
# Only skip when the ENTIRE sweep was transiently unavailable (throttling /
176+
# network) -- never when a real assertion failed (those are already recorded
177+
# as subTest failures and must keep the build red).
178+
if verified == 0 and len(unavailable) == total:
179+
self.skipTest(
180+
f"Could not verify any of the {total} resources (all transiently "
181+
f"unavailable): {unavailable}"
182+
)
183+
if unavailable:
184+
print(
185+
f"\n[pineapple live] verified {verified}/{total}; "
186+
f"{len(unavailable)} transiently unavailable: {unavailable}"
187+
)
188+
189+
90190
if __name__ == "__main__":
91191
unittest.main()

0 commit comments

Comments
 (0)