@@ -379,7 +379,7 @@ def test_search_routes_valid_edge_coordinates(client: TestClient, sample_itinera
379379
380380
381381def test_search_routes_without_ai_description (client : TestClient , sample_itineraries ):
382- """Test that route response works without ai_description (graceful degradation)."""
382+ """Test that route response works without ai_description in itinerary (graceful degradation)."""
383383 with patch ("app.api.v1.endpoints.routes.routing_service" ) as mock_service :
384384 mock_service .get_itinaries = AsyncMock (return_value = sample_itineraries )
385385
@@ -394,29 +394,30 @@ def test_search_routes_without_ai_description(client: TestClient, sample_itinera
394394 assert response .status_code == 200
395395 data = response .json ()
396396
397- # Verify ai_description is present but can be None
398- assert "ai_description" in data
399- # When not provided, it should be None
400- assert data [ "ai_description" ] is None
397+ # Verify ai_description is not in the response (removed from RouteSearchResponse)
398+ assert "ai_description" not in data
399+ # But it should be in each itinerary
400+ assert "ai_description" in data [ "itineraries" ][ 0 ]
401401
402402
403403def test_search_routes_with_ai_description (client : TestClient , sample_itineraries ):
404- """Test that route response validates correctly when ai_description is provided."""
404+ """Test that itinerary schema validates correctly when ai_description is provided."""
405405 # Test the schema validation directly
406406 from app .schemas .geo import Coordinates
407407 from app .schemas .routes import RouteSearchResponse
408408
409- # Schema should validate with ai_description
409+ # Schema should validate with ai_description in itinerary
410+ itinerary_with_description = sample_itineraries [0 ]
411+ itinerary_with_description .ai_description = "This is a fast route with minimal walking."
412+
410413 response_data = RouteSearchResponse (
411414 origin = Coordinates (latitude = 60.1699 , longitude = 24.9384 ),
412415 destination = Coordinates (latitude = 60.2055 , longitude = 24.6559 ),
413- itineraries = sample_itineraries ,
416+ itineraries = [ itinerary_with_description ] ,
414417 search_time = datetime .now (timezone .utc ),
415- ai_description = "This route offers multiple options with varying travel times." ,
416418 )
417419 assert (
418- response_data .ai_description
419- == "This route offers multiple options with varying travel times."
420+ response_data .itineraries [0 ].ai_description == "This is a fast route with minimal walking."
420421 )
421422
422423 # Schema should validate without ai_description
@@ -426,22 +427,30 @@ def test_search_routes_with_ai_description(client: TestClient, sample_itinerarie
426427 itineraries = sample_itineraries ,
427428 search_time = datetime .now (timezone .utc ),
428429 )
429- assert response_data_no_ai .ai_description is None
430+ # ai_description field removed from RouteSearchResponse
431+ assert not hasattr (response_data_no_ai , "ai_description" ) or (
432+ hasattr (response_data_no_ai , "ai_description" )
433+ and response_data_no_ai .ai_description is None
434+ )
430435
431436
432437def test_search_routes_with_ai_insights_success (client : TestClient , sample_itineraries ):
433- """Test successful route search with AI insights for each leg."""
438+ """Test successful route search with AI insights for each leg and itinerary ."""
434439 with patch ("app.api.v1.endpoints.routes.routing_service" ) as mock_routing_service :
435440 with patch ("app.api.v1.endpoints.routes.ai_agents_service" ) as mock_ai_service :
436441 mock_routing_service .get_itinaries = AsyncMock (return_value = sample_itineraries )
437442
438- # Mock AI service to return insights
439- async def mock_get_insight (leg ):
440- if leg .mode == TransportMode .WALK :
441- return "Short walk to the bus stop."
442- return "Express bus with comfortable seats."
443+ # Mock AI service to enrich itinerary in place
444+ async def mock_get_itinerary_insight (itinerary ):
445+ itinerary .ai_description = (
446+ "This route offers a good balance of walking and public transport."
447+ )
448+ itinerary .legs [0 ].ai_insight = "Short walk to the bus stop."
449+ itinerary .legs [1 ].ai_insight = "Express bus with comfortable seats."
443450
444- mock_ai_service .get_route_insight = AsyncMock (side_effect = mock_get_insight )
451+ mock_ai_service .get_itinerary_insight = AsyncMock (
452+ side_effect = mock_get_itinerary_insight
453+ )
445454
446455 response = client .post (
447456 "/api/v1/routes/search" ,
@@ -465,8 +474,13 @@ async def mock_get_insight(leg):
465474 # Check second leg (BUS)
466475 assert itinerary ["legs" ][1 ]["ai_insight" ] == "Express bus with comfortable seats."
467476
468- # Verify AI service was called for each leg
469- assert mock_ai_service .get_route_insight .call_count == 2
477+ # Verify AI description for the itinerary
478+ assert (
479+ itinerary ["ai_description" ]
480+ == "This route offers a good balance of walking and public transport."
481+ )
482+ # Verify AI service was called for itinerary insight
483+ assert mock_ai_service .get_itinerary_insight .call_count == 1
470484
471485
472486def test_search_routes_with_ai_service_unavailable (client : TestClient , sample_itineraries ):
@@ -475,8 +489,14 @@ def test_search_routes_with_ai_service_unavailable(client: TestClient, sample_it
475489 with patch ("app.api.v1.endpoints.routes.ai_agents_service" ) as mock_ai_service :
476490 mock_routing_service .get_itinaries = AsyncMock (return_value = sample_itineraries )
477491
478- # Mock AI service to return None (service unavailable)
479- mock_ai_service .get_route_insight = AsyncMock (return_value = None )
492+ # Mock AI service to do nothing (service unavailable)
493+ async def mock_get_itinerary_insight_unavailable (itinerary ):
494+ # Service unavailable - does not modify itinerary
495+ pass
496+
497+ mock_ai_service .get_itinerary_insight = AsyncMock (
498+ side_effect = mock_get_itinerary_insight_unavailable
499+ )
480500
481501 response = client .post (
482502 "/api/v1/routes/search" ,
@@ -498,25 +518,24 @@ def test_search_routes_with_ai_service_unavailable(client: TestClient, sample_it
498518 # AI insights should be None
499519 assert itinerary ["legs" ][0 ]["ai_insight" ] is None
500520 assert itinerary ["legs" ][1 ]["ai_insight" ] is None
521+ # AI description should be None
522+ assert itinerary ["ai_description" ] is None
501523
502524
503525def test_search_routes_with_ai_service_partial_failure (client : TestClient , sample_itineraries ):
504- """Test graceful degradation when AI service fails for some legs ."""
526+ """Test graceful degradation when AI service provides partial data ."""
505527 with patch ("app.api.v1.endpoints.routes.routing_service" ) as mock_routing_service :
506528 with patch ("app.api.v1.endpoints.routes.ai_agents_service" ) as mock_ai_service :
507529 mock_routing_service .get_itinaries = AsyncMock (return_value = sample_itineraries )
508530
509- # Mock AI service to succeed for first leg, fail for second
510- call_count = 0
511-
512- async def mock_get_insight_partial (leg ):
513- nonlocal call_count
514- call_count += 1
515- if call_count == 1 :
516- return "Short walk to the bus stop."
517- return None # Simulate failure for second leg
531+ # Mock AI service to provide partial data
532+ async def mock_get_itinerary_insight_partial (itinerary ):
533+ # Only set description, not leg insights
534+ itinerary .ai_description = "This is a good route."
518535
519- mock_ai_service .get_route_insight = AsyncMock (side_effect = mock_get_insight_partial )
536+ mock_ai_service .get_itinerary_insight = AsyncMock (
537+ side_effect = mock_get_itinerary_insight_partial
538+ )
520539
521540 response = client .post (
522541 "/api/v1/routes/search" ,
@@ -529,10 +548,9 @@ async def mock_get_insight_partial(leg):
529548 assert response .status_code == 200
530549 data = response .json ()
531550
532- # Verify first leg has insight
533- assert data ["itineraries" ][0 ]["legs" ][0 ]["ai_insight" ] == "Short walk to the bus stop."
534-
535- # Verify second leg has no insight (graceful degradation)
551+ # Verify itinerary has description but legs don't have insights
552+ assert data ["itineraries" ][0 ]["ai_description" ] == "This is a good route."
553+ assert data ["itineraries" ][0 ]["legs" ][0 ]["ai_insight" ] is None
536554 assert data ["itineraries" ][0 ]["legs" ][1 ]["ai_insight" ] is None
537555
538556
@@ -543,7 +561,9 @@ def test_search_routes_with_ai_service_exception(client: TestClient, sample_itin
543561 mock_routing_service .get_itinaries = AsyncMock (return_value = sample_itineraries )
544562
545563 # Mock AI service to raise an exception
546- mock_ai_service .get_route_insight = AsyncMock (side_effect = Exception ("AI service error" ))
564+ mock_ai_service .get_itinerary_insight = AsyncMock (
565+ side_effect = Exception ("AI service error" )
566+ )
547567
548568 response = client .post (
549569 "/api/v1/routes/search" ,
@@ -565,6 +585,8 @@ def test_search_routes_with_ai_service_exception(client: TestClient, sample_itin
565585 # AI insights should be None due to graceful degradation
566586 assert itinerary ["legs" ][0 ]["ai_insight" ] is None
567587 assert itinerary ["legs" ][1 ]["ai_insight" ] is None
588+ # AI description should be None due to graceful degradation
589+ assert itinerary ["ai_description" ] is None
568590
569591
570592def test_leg_schema_with_ai_insight ():
0 commit comments