@@ -170,6 +170,8 @@ async def test_all_collections_api_error(
170170 self , collection_search_client , mock_request , sample_collections_response
171171 ):
172172 """Test handling of API errors."""
173+ from httpx import HTTPStatusError
174+
173175 # One API returns error, one succeeds
174176 respx .get ("https://api1.example.com/collections" ).mock (
175177 return_value = Response (500 , json = {"error" : "Internal server error" })
@@ -179,7 +181,7 @@ async def test_all_collections_api_error(
179181 )
180182
181183 # Should raise exception due to failed API call
182- with pytest .raises (KeyError ):
184+ with pytest .raises (HTTPStatusError ):
183185 await collection_search_client .all_collections (request = mock_request )
184186
185187 @pytest .mark .asyncio
@@ -243,30 +245,40 @@ async def test_all_collections_with_single_api(
243245
244246 @pytest .mark .asyncio
245247 async def test_all_collections_empty_apis_parameter (self , collection_search_client ):
246- """Test collection search with empty apis parameter raises ValueError ."""
248+ """Test collection search with empty apis parameter raises HTTPException ."""
247249 from unittest .mock import Mock
248250
251+ from fastapi import HTTPException
252+
249253 # Create a mock request with empty upstream_api_urls in settings
250254 mock_request = Mock ()
251255 mock_request .app .state .settings .upstream_api_urls = []
252256
253- with pytest .raises (ValueError , match = "no apis specified!" ) :
257+ with pytest .raises (HTTPException ) as exc_info :
254258 await collection_search_client .all_collections (request = mock_request , apis = [])
255259
260+ assert exc_info .value .status_code == 400
261+ assert "No APIs specified" in exc_info .value .detail
262+
256263 @pytest .mark .asyncio
257264 async def test_all_collections_no_apis_fallback_to_settings (
258265 self , collection_search_client
259266 ):
260267 """Test that when no apis parameter provided, it falls back to settings."""
261268 from unittest .mock import Mock
262269
270+ from fastapi import HTTPException
271+
263272 # Create a mock request with empty upstream_api_urls in settings
264273 mock_request = Mock ()
265274 mock_request .app .state .settings .upstream_api_urls = []
266275
267- with pytest .raises (ValueError , match = "no apis specified!" ) :
276+ with pytest .raises (HTTPException ) as exc_info :
268277 await collection_search_client .all_collections (request = mock_request )
269278
279+ assert exc_info .value .status_code == 400
280+ assert "No APIs specified" in exc_info .value .detail
281+
270282 @pytest .mark .asyncio
271283 @respx .mock
272284 async def test_all_collections_apis_parameter_with_pagination (
@@ -319,6 +331,110 @@ async def test_all_collections_apis_parameter_with_search_params(
319331 assert len (result ["collections" ]) == 2
320332 assert result ["numberReturned" ] == 2
321333
334+ @pytest .mark .asyncio
335+ @respx .mock
336+ async def test_apis_parameter_preserved_in_pagination (
337+ self , collection_search_client , mock_request
338+ ):
339+ """Test that apis parameter is preserved when following next link."""
340+ # Mock response with next link for first page
341+ first_page_response = {
342+ "collections" : [
343+ {
344+ "type" : "Collection" ,
345+ "id" : "api3-page1-collection-1" ,
346+ "title" : "Collection Page 1" ,
347+ "description" : "First page collection" ,
348+ "extent" : {
349+ "spatial" : {"bbox" : [[- 180 , - 90 , 180 , 90 ]]},
350+ "temporal" : {
351+ "interval" : [["2020-01-01T00:00:00Z" , "2021-01-01T00:00:00Z" ]]
352+ },
353+ },
354+ "license" : "MIT" ,
355+ "links" : [],
356+ },
357+ ],
358+ "links" : [
359+ {"rel" : "self" , "href" : "https://api3.example.com/collections" },
360+ {
361+ "rel" : "next" ,
362+ "href" : "https://api3.example.com/collections?token=page2" ,
363+ },
364+ ],
365+ }
366+
367+ # Mock response for second page
368+ second_page_response = {
369+ "collections" : [
370+ {
371+ "type" : "Collection" ,
372+ "id" : "api3-page2-collection-1" ,
373+ "title" : "Collection Page 2" ,
374+ "description" : "Second page collection" ,
375+ "extent" : {
376+ "spatial" : {"bbox" : [[- 180 , - 90 , 180 , 90 ]]},
377+ "temporal" : {
378+ "interval" : [["2020-01-01T00:00:00Z" , "2021-01-01T00:00:00Z" ]]
379+ },
380+ },
381+ "license" : "MIT" ,
382+ "links" : [],
383+ },
384+ ],
385+ "links" : [
386+ {
387+ "rel" : "self" ,
388+ "href" : "https://api3.example.com/collections?token=page2" ,
389+ },
390+ {
391+ "rel" : "previous" ,
392+ "href" : "https://api3.example.com/collections" ,
393+ },
394+ ],
395+ }
396+
397+ respx .route (url = "https://api3.example.com/collections?token=page2" ).mock (
398+ return_value = Response (200 , json = second_page_response )
399+ )
400+ respx .route (url = "https://api3.example.com/collections" ).mock (
401+ return_value = Response (200 , json = first_page_response )
402+ )
403+
404+ # First request with custom apis parameter
405+ custom_apis = ["https://api3.example.com" ]
406+ first_result = await collection_search_client .all_collections (
407+ request = mock_request , apis = custom_apis
408+ )
409+
410+ # Extract the next link and token from first page
411+ next_link = next (
412+ (link for link in first_result ["links" ] if link ["rel" ] == "next" ), None
413+ )
414+ assert next_link is not None
415+ assert "token=" in next_link ["href" ]
416+
417+ # Extract token from the next link
418+ token = next_link ["href" ].split ("token=" )[1 ]
419+
420+ # Decode the token to verify apis are preserved
421+ decoded_token = collection_search_client ._decode_token (token )
422+
423+ # The token should contain the current state with the API URL
424+ assert "current" in decoded_token
425+ assert "https://api3.example.com" in decoded_token ["current" ]
426+
427+ # Now follow the next link (simulate second request)
428+ # This should use the token which should have the apis preserved
429+ second_result = await collection_search_client .all_collections (
430+ request = mock_request , token = token
431+ )
432+
433+ # Should have collections from page 2
434+ assert len (second_result ["collections" ]) == 1
435+ collection_ids = [c ["id" ] for c in second_result ["collections" ]]
436+ assert "api3-page2-collection-1" in collection_ids
437+
322438 @pytest .mark .asyncio
323439 async def test_not_implemented_methods (self , collection_search_client ):
324440 """Test that certain methods raise NotImplementedError."""
0 commit comments