@@ -229,69 +229,88 @@ def test_build_instructions_no_customization(mocker: MockerFixture) -> None:
229229# --- Test _get_default_model_id ---
230230
231231
232- def test_get_default_model_id_success (mock_configuration : AppConfig ) -> None :
232+ @pytest .mark .asyncio
233+ async def test_get_default_model_id_success (mock_configuration : AppConfig ) -> None :
233234 """Test _get_default_model_id returns properly formatted model ID."""
234- model_id = _get_default_model_id ()
235+ model_id = await _get_default_model_id ()
235236 assert model_id == "openai/gpt-4-turbo"
236237
237238
238239@pytest .mark .parametrize (
239- ( "config_setup" , "expected_cause" ) ,
240+ "failure_mode" ,
240241 [
241- pytest .param (
242- "missing_model" ,
243- "No default model configured" ,
244- id = "missing_model_config" ,
245- ),
246- pytest .param (
247- "none_inference" ,
248- "No inference configuration available" ,
249- id = "none_inference_config" ,
250- ),
242+ pytest .param ("no_llm_models" , id = "no_llm_models_found" ),
243+ pytest .param ("connection_error" , id = "connection_error" ),
251244 ],
252245)
253- def test_get_default_model_id_errors (
246+ @pytest .mark .asyncio
247+ async def test_get_default_model_id_errors (
254248 mocker : MockerFixture ,
255249 minimal_config : AppConfig ,
256- config_setup : str ,
257- expected_cause : str ,
250+ failure_mode : str ,
258251) -> None :
259- """Test _get_default_model_id raises HTTPException with ServiceUnavailableResponse shape."""
260- if config_setup == "missing_model" :
261- # Config exists but no model/provider defaults
262- mocker .patch ("app.endpoints.rlsapi_v1.configuration" , minimal_config )
252+ """Test _get_default_model_id fallback failures raise 503 responses."""
253+ mocker .patch ("app.endpoints.rlsapi_v1.configuration" , minimal_config )
254+
255+ mock_embedding_model = mocker .Mock ()
256+ mock_embedding_model .custom_metadata = {"model_type" : "embedding" }
257+ mock_embedding_model .id = "sentence-transformers/all-mpnet-base-v2"
258+
259+ mock_client = mocker .Mock ()
260+ mock_client .models = mocker .Mock ()
261+
262+ if failure_mode == "no_llm_models" :
263+ mock_client .models .list = mocker .AsyncMock (return_value = [mock_embedding_model ])
263264 else :
264- # inference is None
265- mock_config = mocker .Mock ()
266- mock_config .inference = None
267- mocker .patch ("app.endpoints.rlsapi_v1.configuration" , mock_config )
265+ mock_client .models .list = mocker .AsyncMock (
266+ side_effect = APIConnectionError (request = mocker .Mock ())
267+ )
268+
269+ mock_client_holder = mocker .Mock ()
270+ mock_client_holder .get_client .return_value = mock_client
271+ mocker .patch (
272+ "app.endpoints.rlsapi_v1.AsyncLlamaStackClientHolder" ,
273+ return_value = mock_client_holder ,
274+ )
268275
269276 with pytest .raises (HTTPException ) as exc_info :
270- _get_default_model_id ()
277+ await _get_default_model_id ()
271278
272279 assert exc_info .value .status_code == 503
273- assert expected_cause in str (exc_info .value .detail )
274- # Verify ServiceUnavailableResponse produces dict with response+cause keys
275280 detail : dict [str , str ] = exc_info .value .detail # type: ignore[assignment]
276281 assert set (detail .keys ()) == {"response" , "cause" }
277282
278283
279- def test_config_error_503_matches_llm_error_503_shape (
284+ @pytest .mark .asyncio
285+ async def test_config_error_503_matches_llm_error_503_shape (
280286 mocker : MockerFixture ,
287+ minimal_config : AppConfig ,
281288) -> None :
282- """Test that configuration error 503s have the same shape as LLM error 503s.
289+ """Test that auto-discovery 503s have the same shape as LLM error 503s.
283290
284- Both _get_default_model_id() configuration errors and APIConnectionError
291+ Both _get_default_model_id() no-LLM auto-discovery errors and APIConnectionError
285292 handlers use ServiceUnavailableResponse, producing identical detail shapes
286293 with 'response' and 'cause' keys.
287294 """
288- # Trigger a configuration error 503
289- mock_config = mocker .Mock ()
290- mock_config .inference = None
291- mocker .patch ("app.endpoints.rlsapi_v1.configuration" , mock_config )
295+ mocker .patch ("app.endpoints.rlsapi_v1.configuration" , minimal_config )
296+
297+ mock_embedding_model = mocker .Mock ()
298+ mock_embedding_model .custom_metadata = {"model_type" : "embedding" }
299+ mock_embedding_model .id = "sentence-transformers/all-mpnet-base-v2"
300+
301+ mock_client = mocker .Mock ()
302+ mock_client .models = mocker .Mock ()
303+ mock_client .models .list = mocker .AsyncMock (return_value = [mock_embedding_model ])
304+
305+ mock_client_holder = mocker .Mock ()
306+ mock_client_holder .get_client .return_value = mock_client
307+ mocker .patch (
308+ "app.endpoints.rlsapi_v1.AsyncLlamaStackClientHolder" ,
309+ return_value = mock_client_holder ,
310+ )
292311
293312 with pytest .raises (HTTPException ) as config_exc :
294- _get_default_model_id ()
313+ await _get_default_model_id ()
295314
296315 # Build an LLM connection error 503 using the same response model
297316 llm_response = ServiceUnavailableResponse (
@@ -306,6 +325,39 @@ def test_config_error_503_matches_llm_error_503_shape(
306325 assert set (config_detail .keys ()) == set (llm_detail .keys ()) == {"response" , "cause" }
307326
308327
328+ @pytest .mark .asyncio
329+ async def test_get_default_model_id_auto_discovery_success (
330+ mocker : MockerFixture , minimal_config : AppConfig
331+ ) -> None :
332+ """Test _get_default_model_id returns first discovered LLM model ID."""
333+ mocker .patch ("app.endpoints.rlsapi_v1.configuration" , minimal_config )
334+
335+ mock_llm_model = mocker .Mock ()
336+ mock_llm_model .custom_metadata = {"model_type" : "llm" }
337+ mock_llm_model .id = "openai/gpt-4o-mini"
338+
339+ mock_embedding_model = mocker .Mock ()
340+ mock_embedding_model .custom_metadata = {"model_type" : "embedding" }
341+ mock_embedding_model .id = "sentence-transformers/all-mpnet-base-v2"
342+
343+ mock_client = mocker .Mock ()
344+ mock_client .models = mocker .Mock ()
345+ mock_client .models .list = mocker .AsyncMock (
346+ return_value = [mock_embedding_model , mock_llm_model ]
347+ )
348+
349+ mock_client_holder = mocker .Mock ()
350+ mock_client_holder .get_client .return_value = mock_client
351+ mocker .patch (
352+ "app.endpoints.rlsapi_v1.AsyncLlamaStackClientHolder" ,
353+ return_value = mock_client_holder ,
354+ )
355+
356+ model_id = await _get_default_model_id ()
357+
358+ assert model_id == "openai/gpt-4o-mini"
359+
360+
309361# --- Test retrieve_simple_response ---
310362
311363
0 commit comments