""" Tests for the image search module. """ from io import BytesIO from typing import Any, Dict import pytest from slidedeckai.helpers import image_search class _MockResponse: """A tiny response-like object to simulate `requests` responses.""" def __init__( self, *, content: bytes = b'', json_data: Any = None, status_ok: bool = True ) -> None: self.content = content self._json = json_data self._status_ok = status_ok def raise_for_status(self) -> None: """Raise an exception when status is not OK.""" if not self._status_ok: raise RuntimeError('status not ok') def json(self) -> Any: """Return preconfigured JSON data.""" return self._json def _dummy_requests_get_success_search( url: str, headers: Dict[str, str], params: Dict[str, Any], timeout: int ): """Return a successful mock response for search_pexels.""" # Validate that the function under test passes expected args assert 'Authorization' in headers assert 'User-Agent' in headers assert 'query' in params photos = [ { 'url': 'https://pexels.com/photo/1', 'src': {'large': 'https://images/1_large.jpg'} }, { 'url': 'https://pexels.com/photo/2', 'src': {'original': 'https://images/2_original.jpg'} }, { 'url': 'https://pexels.com/photo/3', 'src': {'large': 'https://images/3_large.jpg'} } ] return _MockResponse(json_data={'photos': photos}) def _dummy_requests_get_image( url: str, headers: Dict[str, str], stream: bool, timeout: int ): """Return a mock image response for get_image_from_url.""" assert stream is True assert 'Authorization' in headers data = b'\x89PNG\r\n\x1a\n...' return _MockResponse(content=data) def test_extract_dimensions_with_params() -> None: """Extract_dimensions extracts width and height from URL query params.""" url = 'https://images.example.com/photo.jpg?w=800&h=600' width, height = image_search.extract_dimensions(url) assert isinstance(width, int) assert isinstance(height, int) assert (width, height) == (800, 600) def test_extract_dimensions_missing_params() -> None: """When dimensions are missing the function returns (0, 0).""" url = 'https://images.example.com/photo.jpg' assert image_search.extract_dimensions(url) == (0, 0) def test_get_photo_url_from_api_response_none() -> None: """Returns (None, None) when there are no photos in the response.""" result = image_search.get_photo_url_from_api_response({'not_photos': []}) assert result == (None, None) def test_get_photo_url_from_api_response_selects_large_and_original(monkeypatch) -> None: """Ensure the function picks the expected photo and returns correct URLs. This test patches random.choice to deterministically pick indices that exercise the 'large' and 'original' branches. """ photos = [ {'url': 'https://pexels.com/photo/1', 'src': {'large': 'https://images/1_large.jpg'}}, {'url': 'https://pexels.com/photo/2', 'src': {'original': 'https://images/2_original.jpg'}}, {'url': 'https://pexels.com/photo/3', 'src': {'large': 'https://images/3_large.jpg'}}, ] # Ensure the Pexels API key is present so the helper will attempt to select # and return photo URLs rather than early-returning (None, None). monkeypatch.setenv('PEXEL_API_KEY', 'akey') # Force selection of index 1 (second photo) which only has 'original' monkeypatch.setattr(image_search.random, 'choice', lambda seq: 1) photo_url, page_url = image_search.get_photo_url_from_api_response({'photos': photos}) assert page_url == 'https://pexels.com/photo/2' assert photo_url == 'https://images/2_original.jpg' # Force selection of index 0 which has 'large' monkeypatch.setattr(image_search.random, 'choice', lambda seq: 0) photo_url, page_url = image_search.get_photo_url_from_api_response({'photos': photos}) assert page_url == 'https://pexels.com/photo/1' assert photo_url == 'https://images/1_large.jpg' def test_get_image_from_url_success(monkeypatch) -> None: """get_image_from_url returns a BytesIO object with image content.""" monkeypatch.setattr( 'slidedeckai.helpers.image_search.requests.get', lambda *a, **k: _dummy_requests_get_image(*a, **k) ) monkeypatch.setenv('PEXEL_API_KEY', 'dummykey') img = image_search.get_image_from_url('https://images/1_large.jpg') assert isinstance(img, BytesIO) data = img.getvalue() assert data.startswith(b'\x89PNG') def test_search_pexels_success(monkeypatch) -> None: """search_pexels forwards the request and returns parsed JSON.""" monkeypatch.setattr( 'slidedeckai.helpers.image_search.requests.get', lambda *a, **k: _dummy_requests_get_success_search(*a, **k) ) monkeypatch.setenv('PEXEL_API_KEY', 'akey') result = image_search.search_pexels(query='people', size='medium', per_page=3) assert isinstance(result, dict) assert 'photos' in result assert len(result['photos']) == 3 def test_search_pexels_raises_on_request_error(monkeypatch) -> None: """When requests.get raises an exception, it should propagate from search_pexels.""" def _raise(*a, **k): raise RuntimeError('network') monkeypatch.setattr('slidedeckai.helpers.image_search.requests.get', _raise) monkeypatch.setenv('PEXEL_API_KEY', 'akey') with pytest.raises(RuntimeError): image_search.search_pexels(query='x') def test_search_pexels_returns_empty_when_no_api_key(monkeypatch) -> None: """When PEXEL_API_KEY is not set, search_pexels should return an empty dict.""" monkeypatch.delenv('PEXEL_API_KEY', raising=False) result = image_search.search_pexels(query='people') assert result == {} def test_get_photo_url_from_api_response_returns_none_when_no_api_key(monkeypatch) -> None: """When PEXEL_API_KEY is not set, get_photo_url_from_api_response should return (None, None).""" photos = [ {'url': 'https://pexels.com/photo/1', 'src': {'large': 'https://images/1_large.jpg'}} ] monkeypatch.delenv('PEXEL_API_KEY', raising=False) result = image_search.get_photo_url_from_api_response({'photos': photos}) assert result == (None, None)