Skip to content

Commit 4f80b77

Browse files
beaufourclaude
andcommitted
Add tests for bulk metadata fetch optimization
- Add tests for _get_url_from_extras() function - Add tests for _get_extension_from_url() function - Add tests for _download_file() function - Add test for download using prefetched URL flow Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 62be606 commit 4f80b77

1 file changed

Lines changed: 172 additions & 0 deletions

File tree

tests/test_flick_download.py

Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,142 @@
1010
from flickr_api.flickrerrors import FlickrAPIError, FlickrError
1111

1212
from flickr_download.flick_download import (
13+
_download_file,
14+
_get_extension_from_url,
1315
_get_metadata_db,
16+
_get_url_from_extras,
1417
_load_defaults,
1518
do_download_photo,
1619
download_list,
1720
find_user,
1821
)
1922

2023

24+
class TestGetUrlFromExtras:
25+
"""Tests for _get_url_from_extras function."""
26+
27+
def test_get_url_with_specific_size_label(self) -> None:
28+
"""Returns URL for specific size label."""
29+
mock_photo = Mock()
30+
mock_photo.get = Mock(
31+
side_effect=lambda k: {"url_l": "https://example.com/large.jpg"}.get(k)
32+
)
33+
34+
result = _get_url_from_extras(mock_photo, "Large")
35+
assert result == "https://example.com/large.jpg"
36+
37+
def test_get_url_largest_available(self) -> None:
38+
"""Returns largest available URL when no size specified."""
39+
mock_photo = Mock()
40+
# Only medium size available
41+
mock_photo.get = Mock(
42+
side_effect=lambda k: {"url_m": "https://example.com/medium.jpg"}.get(k)
43+
)
44+
45+
result = _get_url_from_extras(mock_photo, None)
46+
assert result == "https://example.com/medium.jpg"
47+
48+
def test_get_url_prefers_original(self) -> None:
49+
"""Prefers original URL when available."""
50+
mock_photo = Mock()
51+
mock_photo.get = Mock(
52+
side_effect=lambda k: {
53+
"url_o": "https://example.com/original.jpg",
54+
"url_l": "https://example.com/large.jpg",
55+
}.get(k)
56+
)
57+
58+
result = _get_url_from_extras(mock_photo, None)
59+
assert result == "https://example.com/original.jpg"
60+
61+
def test_returns_none_when_no_urls(self) -> None:
62+
"""Returns None when no URLs in extras."""
63+
mock_photo = Mock()
64+
mock_photo.get = Mock(return_value=None)
65+
66+
result = _get_url_from_extras(mock_photo, None)
67+
assert result is None
68+
69+
def test_returns_none_for_unknown_size_label(self) -> None:
70+
"""Returns None for unknown size label."""
71+
mock_photo = Mock()
72+
mock_photo.get = Mock(return_value=None)
73+
74+
result = _get_url_from_extras(mock_photo, "UnknownSize")
75+
assert result is None
76+
77+
78+
class TestGetExtensionFromUrl:
79+
"""Tests for _get_extension_from_url function."""
80+
81+
def test_extracts_jpg_extension(self) -> None:
82+
"""Extracts .jpg extension from URL."""
83+
url = "https://farm1.staticflickr.com/123/456_abc_o.jpg"
84+
assert _get_extension_from_url(url) == ".jpg"
85+
86+
def test_extracts_png_extension(self) -> None:
87+
"""Extracts .png extension from URL."""
88+
url = "https://farm1.staticflickr.com/123/456_abc_o.png"
89+
assert _get_extension_from_url(url) == ".png"
90+
91+
def test_handles_query_string(self) -> None:
92+
"""Ignores query string when extracting extension."""
93+
url = "https://farm1.staticflickr.com/123/456_abc_o.jpg?size=large"
94+
assert _get_extension_from_url(url) == ".jpg"
95+
96+
def test_defaults_to_jpg_when_no_extension(self) -> None:
97+
"""Defaults to .jpg when URL has no extension."""
98+
url = "https://farm1.staticflickr.com/123/456_abc_o"
99+
assert _get_extension_from_url(url) == ".jpg"
100+
101+
102+
class TestDownloadFile:
103+
"""Tests for _download_file function."""
104+
105+
@patch("flickr_download.flick_download.requests.get")
106+
def test_downloads_file_successfully(self, mock_get: Mock) -> None:
107+
"""Downloads file from URL to local path."""
108+
mock_response = Mock()
109+
mock_response.iter_content = Mock(return_value=[b"test content"])
110+
mock_response.raise_for_status = Mock()
111+
mock_get.return_value = mock_response
112+
113+
with tempfile.NamedTemporaryFile(delete=False) as f:
114+
fname = f.name
115+
116+
try:
117+
_download_file("https://example.com/photo.jpg", fname)
118+
119+
mock_get.assert_called_once_with(
120+
"https://example.com/photo.jpg", stream=True, timeout=60
121+
)
122+
with open(fname, "rb") as f:
123+
assert f.read() == b"test content"
124+
finally:
125+
Path(fname).unlink(missing_ok=True)
126+
127+
@patch("flickr_download.flick_download.requests.get")
128+
def test_raises_on_http_error(self, mock_get: Mock) -> None:
129+
"""Raises exception on HTTP error."""
130+
import requests
131+
132+
mock_response = Mock()
133+
mock_response.raise_for_status = Mock(side_effect=requests.HTTPError("404 Not Found"))
134+
mock_get.return_value = mock_response
135+
136+
with tempfile.NamedTemporaryFile(delete=False) as f:
137+
fname = f.name
138+
139+
try:
140+
try:
141+
_download_file("https://example.com/notfound.jpg", fname)
142+
assert False, "Expected HTTPError"
143+
except requests.HTTPError:
144+
pass # Expected
145+
finally:
146+
Path(fname).unlink(missing_ok=True)
147+
148+
21149
class TestFindUser:
22150
"""Tests for find_user function."""
23151

@@ -342,6 +470,50 @@ def mock_get_filename(pset: object, photo: object, suffix: Optional[str]) -> str
342470
# Should not call save with skip_download=True
343471
mock_photo.save.assert_not_called()
344472

473+
@patch("flickr_download.flick_download._download_file")
474+
@patch("flickr_download.flick_download.set_file_time")
475+
def test_download_uses_prefetched_url(
476+
self, mock_set_file_time: Mock, mock_download_file: Mock
477+
) -> None:
478+
"""do_download_photo uses pre-fetched URL when available."""
479+
with tempfile.TemporaryDirectory() as tmpdir:
480+
mock_photo = Mock()
481+
mock_photo.id = "123"
482+
mock_photo.title = "Test Photo"
483+
mock_photo.__getitem__ = Mock(
484+
side_effect=lambda k: {
485+
"loaded": True,
486+
"taken": "2020-01-01 12:00:00",
487+
}.get(k)
488+
)
489+
# Return prefetched URL
490+
mock_photo.get = Mock(
491+
side_effect=lambda k: {"url_o": "https://example.com/original.jpg"}.get(k)
492+
)
493+
mock_photo.save = Mock()
494+
495+
mock_pset = Mock()
496+
mock_pset.title = "Test Set"
497+
498+
def mock_get_filename(pset: object, photo: object, suffix: Optional[str]) -> str:
499+
return "Test Photo"
500+
501+
do_download_photo(
502+
tmpdir,
503+
mock_pset,
504+
mock_photo,
505+
None,
506+
"",
507+
mock_get_filename,
508+
)
509+
510+
# Should use _download_file instead of photo.save
511+
mock_download_file.assert_called_once()
512+
mock_photo.save.assert_not_called()
513+
# Verify correct URL was used
514+
call_args = mock_download_file.call_args[0]
515+
assert call_args[0] == "https://example.com/original.jpg"
516+
345517

346518
class TestDownloadList:
347519
"""Tests for download_list function."""

0 commit comments

Comments
 (0)