# #region Test.Assistant.LlmEdge [C:2] [TYPE Module] [SEMANTICS test,assistant,llm,edge,provider,status] # @BRIEF Edge-case tests for _tool_llm.py — provider listing, status checks with various states. # @RELATION BINDS_TO -> [AssistantToolLlm] # @TEST_CONTRACT: handle_list_llm_providers -> lists providers with icons and model info # @TEST_CONTRACT: handle_get_llm_status -> shows provider count and valid/invalid key states # @TEST_EDGE: get_llm_status_mixed_providers -> active + inactive displayed correctly # @TEST_EDGE: get_llm_status_invalid_api_key -> shows "невалидный" status # @TEST_EDGE: list_providers_with_null_model -> shows "модель не указана" # @TEST_EDGE: get_llm_status_multiple_active -> picks first active provider from datetime import UTC, datetime from unittest.mock import AsyncMock, MagicMock, patch import pytest from src.schemas.auth import User @pytest.fixture def current_user(): return User( id="user-1", username="testuser", email="test@example.com", is_active=True, auth_source="internal", created_at=datetime.now(UTC), last_login=datetime.now(UTC), roles=[], ) @pytest.fixture def config_manager(): return MagicMock() @pytest.fixture def task_manager(): return MagicMock() @pytest.fixture def db(): return MagicMock() @pytest.fixture def intent(): return {"entities": {}} class TestListLlmProvidersEdge: """handle_list_llm_providers — edge cases.""" @pytest.mark.asyncio async def test_single_provider( self, intent, current_user, task_manager, config_manager, db ): from src.api.routes.assistant._tool_llm import handle_list_llm_providers with patch("src.api.routes.assistant._tool_llm.LLMProviderService") as mock_svc_cls: svc = MagicMock() mock_svc_cls.return_value = svc p = MagicMock(name="OpenAI", provider_type="openai", is_active=True, default_model="gpt-4") svc.get_all_providers.return_value = [p] text, task_id, actions = await handle_list_llm_providers( intent, current_user, task_manager, config_manager, db ) assert "LLM провайдеры" in text assert "OpenAI" in text assert "gpt-4" in text @pytest.mark.asyncio async def test_provider_with_null_model( self, intent, current_user, task_manager, config_manager, db ): from src.api.routes.assistant._tool_llm import handle_list_llm_providers with patch("src.api.routes.assistant._tool_llm.LLMProviderService") as mock_svc_cls: svc = MagicMock() mock_svc_cls.return_value = svc p = MagicMock(name="Anthropic", provider_type="anthropic", is_active=True, default_model=None) svc.get_all_providers.return_value = [p] text, task_id, actions = await handle_list_llm_providers( intent, current_user, task_manager, config_manager, db ) assert "Anthropic" in text assert "модель не указана" in text @pytest.mark.asyncio async def test_all_inactive_providers( self, intent, current_user, task_manager, config_manager, db ): from src.api.routes.assistant._tool_llm import handle_list_llm_providers with patch("src.api.routes.assistant._tool_llm.LLMProviderService") as mock_svc_cls: svc = MagicMock() mock_svc_cls.return_value = svc p1 = MagicMock(name="Provider1", provider_type="type1", is_active=False, default_model="m1") p2 = MagicMock(name="Provider2", provider_type="type2", is_active=False, default_model="m2") svc.get_all_providers.return_value = [p1, p2] text, task_id, actions = await handle_list_llm_providers( intent, current_user, task_manager, config_manager, db ) # Both should appear with inactive icon assert "Provider1" in text assert "Provider2" in text @pytest.mark.asyncio async def test_many_providers( self, intent, current_user, task_manager, config_manager, db ): from src.api.routes.assistant._tool_llm import handle_list_llm_providers with patch("src.api.routes.assistant._tool_llm.LLMProviderService") as mock_svc_cls: svc = MagicMock() mock_svc_cls.return_value = svc providers = [ MagicMock(name=f"P{i}", provider_type=f"type{i}", is_active=i % 2 == 0, default_model=f"m{i}") for i in range(5) ] svc.get_all_providers.return_value = providers text, task_id, actions = await handle_list_llm_providers( intent, current_user, task_manager, config_manager, db ) for i in range(5): assert f"P{i}" in text @pytest.mark.asyncio async def test_action_links_settings( self, intent, current_user, task_manager, config_manager, db ): from src.api.routes.assistant._tool_llm import handle_list_llm_providers with patch("src.api.routes.assistant._tool_llm.LLMProviderService") as mock_svc_cls: svc = MagicMock() mock_svc_cls.return_value = svc svc.get_all_providers.return_value = [ MagicMock(name="O", provider_type="openai", is_active=True, default_model="gpt-4") ] text, task_id, actions = await handle_list_llm_providers( intent, current_user, task_manager, config_manager, db ) assert len(actions) == 1 assert actions[0].type == "open_route" assert "settings/llm" in actions[0].target class TestGetLlmStatusEdge: """handle_get_llm_status — edge cases.""" @pytest.mark.asyncio async def test_multiple_providers_counts( self, intent, current_user, task_manager, config_manager, db ): from src.api.routes.assistant._tool_llm import handle_get_llm_status with patch("src.api.routes.assistant._tool_llm.LLMProviderService") as mock_svc_cls: svc = MagicMock() mock_svc_cls.return_value = svc p1 = MagicMock(id="p1", name="A", provider_type="t1", is_active=True, default_model="m1") p2 = MagicMock(id="p2", name="B", provider_type="t2", is_active=False, default_model=None) p3 = MagicMock(id="p3", name="C", provider_type="t3", is_active=True, default_model="m3") svc.get_all_providers.return_value = [p1, p2, p3] svc.get_decrypted_api_key.return_value = "sk-valid" with patch("src.api.routes.llm._is_valid_runtime_api_key", return_value=True): text, task_id, actions = await handle_get_llm_status( intent, current_user, task_manager, config_manager, db ) assert "Всего провайдеров: 3" in text assert "Активных: 2" in text assert "Неактивных: 1" in text @pytest.mark.asyncio async def test_invalid_api_key( self, intent, current_user, task_manager, config_manager, db ): from src.api.routes.assistant._tool_llm import handle_get_llm_status with patch("src.api.routes.assistant._tool_llm.LLMProviderService") as mock_svc_cls: svc = MagicMock() mock_svc_cls.return_value = svc p = MagicMock(id="p1", name="OpenAI", provider_type="openai", is_active=True, default_model="gpt-4") svc.get_all_providers.return_value = [p] svc.get_decrypted_api_key.return_value = "invalid" with patch("src.api.routes.llm._is_valid_runtime_api_key", return_value=False): text, task_id, actions = await handle_get_llm_status( intent, current_user, task_manager, config_manager, db ) assert "невалидный" in text or "не полностью" in text @pytest.mark.asyncio async def test_active_provider_without_model( self, intent, current_user, task_manager, config_manager, db ): from src.api.routes.assistant._tool_llm import handle_get_llm_status with patch("src.api.routes.assistant._tool_llm.LLMProviderService") as mock_svc_cls: svc = MagicMock() mock_svc_cls.return_value = svc p = MagicMock(id="p1", name="Custom", provider_type="custom", is_active=True, default_model=None) svc.get_all_providers.return_value = [p] svc.get_decrypted_api_key.return_value = "some-key" with patch("src.api.routes.llm._is_valid_runtime_api_key", return_value=True): text, task_id, actions = await handle_get_llm_status( intent, current_user, task_manager, config_manager, db ) assert "не указана" in text @pytest.mark.asyncio async def test_multiple_active_providers_first_one_shown( self, intent, current_user, task_manager, config_manager, db ): from src.api.routes.assistant._tool_llm import handle_get_llm_status with patch("src.api.routes.assistant._tool_llm.LLMProviderService") as mock_svc_cls: svc = MagicMock() mock_svc_cls.return_value = svc p1 = MagicMock(id="p1", name="Alpha", provider_type="type1", is_active=True, default_model="m1") p2 = MagicMock(id="p2", name="Beta", provider_type="type2", is_active=True, default_model="m2") svc.get_all_providers.return_value = [p1, p2] svc.get_decrypted_api_key.return_value = "valid-key" with patch("src.api.routes.llm._is_valid_runtime_api_key", return_value=True): text, task_id, actions = await handle_get_llm_status( intent, current_user, task_manager, config_manager, db ) # Should show the first active provider (Alpha) assert "Alpha" in text or "