@@ -347,6 +347,101 @@ def test_spec_detail_not_found(self, client: TestClient) -> None:
347347 response = client .get ("/specs/nonexistent" )
348348 assert response .status_code == 404
349349
350+ def test_specs_map_without_db (self , client : TestClient ) -> None :
351+ """Specs map should return 503 when DB not configured."""
352+ with patch (DB_CONFIG_PATCH , return_value = False ):
353+ response = client .get ("/specs/map" )
354+ assert response .status_code == 503
355+
356+ def test_specs_map_returns_list (self , client : TestClient , mock_spec ) -> None :
357+ """Specs map returns one row per spec with best-impl preview + tag bag."""
358+ mock_spec_repo = MagicMock ()
359+ mock_spec_repo .get_all = AsyncMock (return_value = [mock_spec ])
360+
361+ with (
362+ patch (DB_CONFIG_PATCH , return_value = True ),
363+ patch ("api.routers.specs.get_or_set_cache" , side_effect = _passthrough_cache ),
364+ patch ("api.routers.specs.SpecRepository" , return_value = mock_spec_repo ),
365+ ):
366+ response = client .get ("/specs/map" )
367+ assert response .status_code == 200
368+ data = response .json ()
369+ assert isinstance (data , list )
370+ assert len (data ) == 1
371+ row = data [0 ]
372+ assert row ["id" ] == "scatter-basic"
373+ assert row ["title" ] == "Basic Scatter Plot"
374+ assert row ["preview_url_light" ] == TEST_IMAGE_URL
375+ assert row ["quality_score" ] == 92.5
376+ assert row ["tags" ] == {
377+ "plot_type" : ["scatter" ],
378+ "domain" : ["statistics" ],
379+ "data_type" : ["numeric" ],
380+ "features" : ["basic" ],
381+ }
382+ assert row ["impl_tags" ] == {"patterns" : ["data-generation" ], "styling" : ["alpha-blending" ]}
383+
384+ def test_specs_map_picks_best_impl (self , client : TestClient , mock_spec ) -> None :
385+ """Specs map picks the impl with the highest quality_score per spec."""
386+ # Append a second, lower-rated impl with a distinct preview URL
387+ worse_impl = MagicMock ()
388+ worse_impl .library_id = "seaborn"
389+ worse_impl .preview_url_light = "https://example.com/worse-light.png"
390+ worse_impl .preview_url_dark = None
391+ worse_impl .quality_score = 60.0
392+ worse_impl .impl_tags = {"patterns" : ["should-not-appear" ]}
393+ mock_spec .impls = [worse_impl , mock_spec .impls [0 ]] # quality 60 then quality 92.5
394+
395+ mock_spec_repo = MagicMock ()
396+ mock_spec_repo .get_all = AsyncMock (return_value = [mock_spec ])
397+
398+ with (
399+ patch (DB_CONFIG_PATCH , return_value = True ),
400+ patch ("api.routers.specs.get_or_set_cache" , side_effect = _passthrough_cache ),
401+ patch ("api.routers.specs.SpecRepository" , return_value = mock_spec_repo ),
402+ ):
403+ response = client .get ("/specs/map" )
404+ assert response .status_code == 200
405+ data = response .json ()
406+ assert len (data ) == 1
407+ assert data [0 ]["preview_url_light" ] == TEST_IMAGE_URL # higher-rated matplotlib impl
408+ assert data [0 ]["quality_score" ] == 92.5
409+ assert data [0 ]["impl_tags" ] == {"patterns" : ["data-generation" ], "styling" : ["alpha-blending" ]}
410+
411+ def test_specs_map_skips_specs_without_impls (self , client : TestClient , mock_spec ) -> None :
412+ """Specs map omits specs with zero implementations (matches /specs behavior)."""
413+ empty_spec = MagicMock ()
414+ empty_spec .id = "no-impls"
415+ empty_spec .title = "No Implementations Yet"
416+ empty_spec .impls = []
417+
418+ mock_spec_repo = MagicMock ()
419+ mock_spec_repo .get_all = AsyncMock (return_value = [mock_spec , empty_spec ])
420+
421+ with (
422+ patch (DB_CONFIG_PATCH , return_value = True ),
423+ patch ("api.routers.specs.get_or_set_cache" , side_effect = _passthrough_cache ),
424+ patch ("api.routers.specs.SpecRepository" , return_value = mock_spec_repo ),
425+ ):
426+ response = client .get ("/specs/map" )
427+ assert response .status_code == 200
428+ data = response .json ()
429+ assert [row ["id" ] for row in data ] == ["scatter-basic" ]
430+
431+ def test_specs_map_empty_db (self , client : TestClient ) -> None :
432+ """Specs map returns [] (not 404) when there are no specs."""
433+ mock_spec_repo = MagicMock ()
434+ mock_spec_repo .get_all = AsyncMock (return_value = [])
435+
436+ with (
437+ patch (DB_CONFIG_PATCH , return_value = True ),
438+ patch ("api.routers.specs.get_or_set_cache" , side_effect = _passthrough_cache ),
439+ patch ("api.routers.specs.SpecRepository" , return_value = mock_spec_repo ),
440+ ):
441+ response = client .get ("/specs/map" )
442+ assert response .status_code == 200
443+ assert response .json () == []
444+
350445
351446class TestDownloadRouter :
352447 """Tests for download router."""
0 commit comments