import asyncio import pytest from app.llm import LLMError, LLMService from app.planner import RecommendationService from app.schemas import ModelRecommendationSet, PlanRecommendationRequest def custom_request() -> PlanRecommendationRequest: return PlanRecommendationRequest.model_validate( { "mode": "custom", "preferences": { "destination": "深圳市光明区", "themes": ["朋友"], "duration": "half_day", "pace": "moderate", "interests": ["自然风光"], "adults": 2, "children": 0, "childAges": [], "transport": "public_transport", "budgetLevel": "standard", "extraRequirements": "", }, "durationMinutes": 120, "selectedPoiIds": ["poi_a", "poi_b", "poi_c"], "candidates": [ { "id": poi_id, "name": poi_id, "categoryCode": "urban_park", "tagCodes": ["walking"], "summary": "test", "recommendationIndex": 4, } for poi_id in ["poi_a", "poi_b", "poi_c", "poi_d"] ], } ) class PartialCustomLLM: mode = "mock" async def recommend(self, _request): return ModelRecommendationSet.model_validate( { "recommendations": [ {"poiId": "poi_c", "reason": "先参观 C", "order": 1}, {"poiId": "poi_d", "reason": "模型多选了 D", "order": 2}, ] } ) class UnknownPoiLLM: mode = "mock" async def recommend(self, _request): return ModelRecommendationSet.model_validate( { "recommendations": [ {"poiId": "poi_unknown", "reason": "unknown", "order": 1} ] } ) def test_custom_selection_is_owned_by_user_not_model(): result = asyncio.run(RecommendationService(PartialCustomLLM()).create(custom_request())) ids = [item.poi_id for item in result.recommendations] assert ids == ["poi_c", "poi_a", "poi_b"] assert set(ids) == {"poi_a", "poi_b", "poi_c"} def test_model_output_with_unknown_id_is_rejected(): with pytest.raises(LLMError, match="白名单外"): asyncio.run(RecommendationService(UnknownPoiLLM()).create(custom_request())) def test_model_health_states_distinguish_mode_configuration_and_readiness(monkeypatch): monkeypatch.delenv("OPENAI_API_KEY", raising=False) monkeypatch.delenv("OPENAI_MODEL", raising=False) monkeypatch.setenv("LLM_MODE", "mock") mock = LLMService() assert (mock.mode, mock.configured, mock.ready) == ("mock", False, True) monkeypatch.setenv("LLM_MODE", "real") unconfigured = LLMService() assert (unconfigured.mode, unconfigured.configured, unconfigured.ready) == ( "real", False, False, ) monkeypatch.setenv("OPENAI_API_KEY", "test-only-placeholder") monkeypatch.setenv("OPENAI_MODEL", "test-only-model") configured = LLMService() assert (configured.mode, configured.configured, configured.ready) == ( "real", True, True, )