Files
ss-tools/backend/tests/api/test_assistant_llm_edge.py
root 632b730fff chore: migrate GRACE-Poly anchors to hierarchical dotted naming
Systematic rename of all semantic anchors (#region, [DEF], @RELATION)
across 1400+ files — backend Python, frontend Svelte/TS, specs, docs:
- Flat anchors become Namespace.Module.Entity
- @RELATION references updated to match new anchor paths
- Zero business logic changes
2026-07-22 11:48:15 +03:00

281 lines
12 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# #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 -> [Api.ToolLlm.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 "<MagicMock name='Alpha.name'" in text
# Beta is also active but only first is shown as "Активный провайдер"
@pytest.mark.asyncio
async def test_all_inactive_no_active_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="Inactive1", provider_type="t1", is_active=False, default_model="m1")
p2 = MagicMock(id="p2", name="Inactive2", provider_type="t2", is_active=False, default_model=None)
svc.get_all_providers.return_value = [p1, p2]
text, task_id, actions = await handle_get_llm_status(
intent, current_user, task_manager, config_manager, db
)
assert "Активных: 0" in text
# Should NOT show active provider details block
assert "Активный провайдер" not in text
@pytest.mark.asyncio
async def test_no_providers_no_actions(
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
svc.get_all_providers.return_value = []
text, task_id, actions = await handle_get_llm_status(
intent, current_user, task_manager, config_manager, db
)
assert task_id is None
assert actions == []
# #endregion Test.Assistant.LlmEdge