@@ -201,6 +201,49 @@ def test_libraries_with_db(self, db_client, mock_lib) -> None:
201201 assert len (data ["libraries" ]) == 1
202202 assert data ["libraries" ][0 ]["id" ] == "matplotlib"
203203
204+ def test_libraries_cache_hit (self , db_client ) -> None :
205+ """Libraries should return cached data when available."""
206+ client , _ = db_client
207+
208+ cached_data = {"libraries" : [{"id" : "cached_lib" , "name" : "Cached" }]}
209+
210+ with patch ("api.routers.libraries.get_cache" , return_value = cached_data ):
211+ response = client .get ("/libraries" )
212+ assert response .status_code == 200
213+ data = response .json ()
214+ assert data ["libraries" ][0 ]["id" ] == "cached_lib"
215+
216+ def test_library_images_with_db (self , db_client , mock_spec ) -> None :
217+ """Library images should return images from DB."""
218+ client , _ = db_client
219+
220+ mock_spec_repo = MagicMock ()
221+ mock_spec_repo .get_all = AsyncMock (return_value = [mock_spec ])
222+
223+ with (
224+ patch ("api.routers.libraries.get_cache" , return_value = None ),
225+ patch ("api.routers.libraries.set_cache" ),
226+ patch ("api.routers.libraries.SpecRepository" , return_value = mock_spec_repo ),
227+ ):
228+ response = client .get ("/libraries/matplotlib/images" )
229+ assert response .status_code == 200
230+ data = response .json ()
231+ assert data ["library" ] == "matplotlib"
232+ assert len (data ["images" ]) == 1
233+ assert data ["images" ][0 ]["spec_id" ] == "scatter-basic"
234+
235+ def test_library_images_cache_hit (self , db_client ) -> None :
236+ """Library images should return cached data when available."""
237+ client , _ = db_client
238+
239+ cached_data = {"library" : "matplotlib" , "images" : [{"spec_id" : "cached" }]}
240+
241+ with patch ("api.routers.libraries.get_cache" , return_value = cached_data ):
242+ response = client .get ("/libraries/matplotlib/images" )
243+ assert response .status_code == 200
244+ data = response .json ()
245+ assert data ["images" ][0 ]["spec_id" ] == "cached"
246+
204247
205248class TestSpecsRouter :
206249 """Tests for specs router."""
@@ -305,6 +348,53 @@ def test_download_impl_not_found(self, client: TestClient, mock_spec) -> None:
305348 response = client .get ("/download/scatter-basic/seaborn" )
306349 assert response .status_code == 404
307350
351+ def test_download_success (self , client : TestClient , mock_spec ) -> None :
352+ """Download should return image when spec and impl found."""
353+
354+ mock_spec_repo = MagicMock ()
355+ mock_spec_repo .get_by_id = AsyncMock (return_value = mock_spec )
356+
357+ # Mock httpx response
358+ mock_response = MagicMock ()
359+ mock_response .content = b"fake image content"
360+ mock_response .raise_for_status = MagicMock ()
361+
362+ mock_httpx_client = AsyncMock ()
363+ mock_httpx_client .get = AsyncMock (return_value = mock_response )
364+ mock_httpx_client .__aenter__ = AsyncMock (return_value = mock_httpx_client )
365+ mock_httpx_client .__aexit__ = AsyncMock (return_value = None )
366+
367+ with (
368+ patch (DB_CONFIG_PATCH , return_value = True ),
369+ patch ("api.routers.download.SpecRepository" , return_value = mock_spec_repo ),
370+ patch ("api.routers.download.httpx.AsyncClient" , return_value = mock_httpx_client ),
371+ ):
372+ response = client .get ("/download/scatter-basic/matplotlib" )
373+ assert response .status_code == 200
374+ assert response .headers ["content-type" ] == "image/png"
375+ assert "attachment" in response .headers ["content-disposition" ]
376+ assert response .content == b"fake image content"
377+
378+ def test_download_gcs_error (self , client : TestClient , mock_spec ) -> None :
379+ """Download should return 502 when GCS fetch fails."""
380+ import httpx
381+
382+ mock_spec_repo = MagicMock ()
383+ mock_spec_repo .get_by_id = AsyncMock (return_value = mock_spec )
384+
385+ mock_httpx_client = AsyncMock ()
386+ mock_httpx_client .get = AsyncMock (side_effect = httpx .HTTPError ("GCS error" ))
387+ mock_httpx_client .__aenter__ = AsyncMock (return_value = mock_httpx_client )
388+ mock_httpx_client .__aexit__ = AsyncMock (return_value = None )
389+
390+ with (
391+ patch (DB_CONFIG_PATCH , return_value = True ),
392+ patch ("api.routers.download.SpecRepository" , return_value = mock_spec_repo ),
393+ patch ("api.routers.download.httpx.AsyncClient" , return_value = mock_httpx_client ),
394+ ):
395+ response = client .get ("/download/scatter-basic/matplotlib" )
396+ assert response .status_code == 502
397+
308398
309399class TestSeoRouter :
310400 """Tests for SEO router."""
0 commit comments