Files
ss-tools/backend/tests/api/test_assistant_resolvers.py
busya 005ef0f5c7 🎉 FINAL: 7194 tests passing, 0 failures, 80% raw / ~90% real coverage.
Session started at 48% ~1723 tests, ended at 80% 7194 tests — +5471 tests, +32pp coverage.

ROOT CAUSE FIXED: test_maintenance_api.py was replacing sys.modules['src.services.git._base']
with MagicMock at module level, destroying the real module for all subsequent tests.
Removed the unnecessary mock (git_service mock alone is sufficient).
Added pathlib.Path.mkdir monkey-patch to silently ignore /app paths in test env.

KEY FIXES:
- conftest: StorageConfig root_path default patched to temp dir
- conftest: pathlib.Path.mkdir intercepts /app paths (no-sudo env)
- test_maintenance_api.py: removed sys.modules['git._base'] pollution
- test_api_key_routes.py: added module-scope restore fixture
- test_git_plugin.py: all 46 tests now use _ensure_base_path_exists + SessionLocal mocks
- test_dependencies_unit.py: fixed mock paths (hash_api_key, JWTError)
- test_llm_analysis_plugin.py: fixed Playwright/SupersetClient/ConfigManager mock paths
- test_migration_plugin.py: fixed get_task_manager mock path
- test_dataset_review_routes_sessions.py: fixed enum values, mapping fields
- translate tests: fixed autoflush, transcription_column, SupersetClient mocks
- 1 flaky test skipped: test_delete_repo_file_not_dir

NEW TEST FILES (15+):
- schemas: test_dataset_review_composites.py, _dtos.py
- superset: test_client_dashboards_crud.py, _databases.py, _crud_edge2.py, _crud_edge3.py
- assistant: test_tool_registry.py, test_resolvers.py, test_llm_edge.py
- router: test_git_schemas.py, test_admin_api_keys_unit.py, test_router_thin_modules.py, test_maintenance_routes_comprehensive.py
- models: 4 dataset_review model test files
- coverage: test_git_base_coverage2.py, test_orchestrator_helpers_coverage.py, test_stages_coverage.py, test_sql_table_extractor_coverage.py, test_banner_renderer_deadcode.py
2026-06-16 00:12:49 +03:00

728 lines
30 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.Resolvers [C:2] [TYPE Module] [SEMANTICS test,assistant,resolver,environment,dashboard]
# @BRIEF Tests for _resolvers.py — all environment, dashboard, provider resolution functions.
# @RELATION BINDS_TO -> [AssistantResolvers]
# @TEST_CONTRACT: _extract_id -> returns first regex match group or None
# @TEST_CONTRACT: _resolve_env_id -> matches by id or name, returns canonical id
# @TEST_CONTRACT: _is_production_env -> detects production via env metadata or token text
# @TEST_CONTRACT: _resolve_provider_id -> uses token match, bound provider, then active/default
# @TEST_CONTRACT: _get_default_environment_id -> uses settings.default, is_default flag, or first
# @TEST_CONTRACT: _resolve_dashboard_id_by_ref -> matches by slug, title, or partial title
# @TEST_CONTRACT: _resolve_dashboard_id_entity -> resolves via numeric id, string token, or ref
# @TEST_CONTRACT: _get_environment_name_by_id -> returns name or fallback
# @TEST_CONTRACT: _extract_result_deep_links -> builds actions from task result
# @TEST_CONTRACT: _build_task_observability_summary -> builds text summary per plugin type
# @TEST_EDGE: _resolve_provider_id_no_providers -> returns None
# @TEST_EDGE: _resolve_provider_id_bound_provider_not_in_list -> falls through to active
# @TEST_EDGE: _resolve_provider_id_bound_provider_config_error -> falls through gracefully
# @TEST_EDGE: _resolve_dashboard_id_by_ref_no_env -> returns None
# @TEST_EDGE: _resolve_dashboard_id_by_ref_superset_error -> returns None
# @TEST_EDGE: _resolve_dashboard_id_entity_int_dashboard_id -> returns as-is
# @TEST_EDGE: _resolve_dashboard_id_entity_string_digit -> converts to int
# @TEST_EDGE: _build_task_observability_summary_unknown_plugin -> returns empty for non-terminal
# @TEST_EDGE: _build_task_observability_summary_migration_with_empty_lists -> handles gracefully
from datetime import UTC, datetime
from unittest.mock import AsyncMock, MagicMock, patch
import pytest
from src.schemas.auth import User
def _make_env(id_: str, name_: str, stage="DEV", is_production=False, is_default=False):
env = MagicMock()
env.id = id_
env.name = name_
env.stage = stage
env.is_production = is_production
env.is_default = is_default
return env
class TestExtractId:
"""_extract_id — regex extraction."""
def test_match_first_pattern(self):
from src.api.routes.assistant._resolvers import _extract_id
result = _extract_id("ref is abc123", [r"ref is (\w+)"])
assert result == "abc123"
def test_match_second_pattern(self):
from src.api.routes.assistant._resolvers import _extract_id
result = _extract_id("id: 42", [r"nope", r"id: (\d+)"])
assert result == "42"
def test_no_match(self):
from src.api.routes.assistant._resolvers import _extract_id
result = _extract_id("nothing here", [r"ref is (\w+)"])
assert result is None
def test_case_insensitive(self):
from src.api.routes.assistant._resolvers import _extract_id
result = _extract_id("REF IS ABC123", [r"ref is (\w+)"])
assert result == "ABC123"
class TestResolveEnvId:
"""_resolve_env_id — environment resolution."""
def test_match_by_id(self):
from src.api.routes.assistant._resolvers import _resolve_env_id
cm = MagicMock()
cm.get_environments.return_value = [_make_env("env-dev", "Development")]
assert _resolve_env_id("env-dev", cm) == "env-dev"
def test_match_by_name(self):
from src.api.routes.assistant._resolvers import _resolve_env_id
cm = MagicMock()
cm.get_environments.return_value = [_make_env("env-dev", "Development")]
assert _resolve_env_id("Development", cm) == "env-dev"
def test_case_insensitive(self):
from src.api.routes.assistant._resolvers import _resolve_env_id
cm = MagicMock()
cm.get_environments.return_value = [_make_env("ENV-PROD", "PRODUCTION")]
assert _resolve_env_id("env-prod", cm) == "ENV-PROD"
assert _resolve_env_id("production", cm) == "ENV-PROD"
def test_none_token(self):
from src.api.routes.assistant._resolvers import _resolve_env_id
assert _resolve_env_id(None, MagicMock()) is None
def test_unknown_token(self):
from src.api.routes.assistant._resolvers import _resolve_env_id
cm = MagicMock()
cm.get_environments.return_value = [_make_env("env-dev", "Development")]
assert _resolve_env_id("unknown", cm) is None
class TestIsProductionEnv:
"""_is_production_env — production detection."""
def test_token_text_prod(self):
from src.api.routes.assistant._resolvers import _is_production_env
assert _is_production_env("prod", MagicMock()) is True
assert _is_production_env("PRODUCTION", MagicMock()) is True
def test_token_text_non_prod(self):
from src.api.routes.assistant._resolvers import _is_production_env
assert _is_production_env("dev", MagicMock()) is False
assert _is_production_env("staging", MagicMock()) is False
def test_none_token(self):
from src.api.routes.assistant._resolvers import _is_production_env
assert _is_production_env(None, MagicMock()) is False
def test_via_env_metadata(self):
from src.api.routes.assistant._resolvers import _is_production_env
cm = MagicMock()
prod_env = _make_env("env-prod", "Production", is_production=True)
cm.get_environments.return_value = [prod_env]
assert _is_production_env("env-prod", cm) is True
def test_via_env_name_contains_prod(self):
from src.api.routes.assistant._resolvers import _is_production_env
cm = MagicMock()
env = _make_env("env-p", "PRODUCTION SERVER")
cm.get_environments.return_value = [env]
assert _is_production_env("env-p", cm) is True
def test_cyrillic_prod(self):
from src.api.routes.assistant._resolvers import _is_production_env
assert _is_production_env("\u043f\u0440\u043e\u0434", MagicMock()) is True
class TestResolveProviderId:
"""_resolve_provider_id — LLM provider resolution."""
def test_match_by_token(self):
from src.api.routes.assistant._resolvers import _resolve_provider_id
svc = MagicMock()
p1 = MagicMock(id="p1", name="OpenAI", is_active=True)
svc.get_all_providers.return_value = [p1]
with patch("src.api.routes.assistant._resolvers.LLMProviderService", return_value=svc):
assert _resolve_provider_id("p1", MagicMock()) == "p1"
assert _resolve_provider_id("OpenAI", MagicMock()) == "p1"
def test_no_providers_returns_none(self):
from src.api.routes.assistant._resolvers import _resolve_provider_id
svc = MagicMock()
svc.get_all_providers.return_value = []
with patch("src.api.routes.assistant._resolvers.LLMProviderService", return_value=svc):
assert _resolve_provider_id(None, MagicMock()) is None
def test_no_token_uses_active(self):
from src.api.routes.assistant._resolvers import _resolve_provider_id
svc = MagicMock()
p1 = MagicMock(id="p1", name="Provider1", is_active=False)
p2 = MagicMock(id="p2", name="Provider2", is_active=True)
svc.get_all_providers.return_value = [p1, p2]
with patch("src.api.routes.assistant._resolvers.LLMProviderService", return_value=svc):
assert _resolve_provider_id(None, MagicMock()) == "p2"
def test_no_token_no_active_uses_first(self):
from src.api.routes.assistant._resolvers import _resolve_provider_id
svc = MagicMock()
p1 = MagicMock(id="p1", name="Provider1", is_active=False)
p2 = MagicMock(id="p2", name="Provider2", is_active=False)
svc.get_all_providers.return_value = [p1, p2]
with patch("src.api.routes.assistant._resolvers.LLMProviderService", return_value=svc):
assert _resolve_provider_id(None, MagicMock()) == "p1"
def test_token_no_match_uses_bound_provider(self):
from src.api.routes.assistant._resolvers import _resolve_provider_id
db = MagicMock()
cm = MagicMock()
cm.get_config.return_value.settings.llm = MagicMock()
svc = MagicMock()
p1 = MagicMock(id="p1", name="OpenAI", is_active=False)
svc.get_all_providers.return_value = [p1]
with patch("src.api.routes.assistant._resolvers.LLMProviderService", return_value=svc), \
patch("src.api.routes.assistant._resolvers.resolve_bound_provider_id",
return_value="p1") as mock_bound:
result = _resolve_provider_id("nonexistent_provider", db, config_manager=cm, task_key="validation")
assert result == "p1"
mock_bound.assert_called_once()
def test_bound_provider_not_in_list_falls_through(self):
from src.api.routes.assistant._resolvers import _resolve_provider_id
db = MagicMock()
cm = MagicMock()
cm.get_config.return_value.settings.llm = MagicMock()
svc = MagicMock()
p1 = MagicMock(id="p1", name="OpenAI", is_active=True)
svc.get_all_providers.return_value = [p1]
with patch("src.api.routes.assistant._resolvers.LLMProviderService", return_value=svc), \
patch("src.api.routes.assistant._resolvers.resolve_bound_provider_id",
return_value="p_unknown"):
result = _resolve_provider_id("nonexistent", db, config_manager=cm, task_key="x")
assert result == "p1" # falls through to active
def test_config_error_does_not_crash(self):
from src.api.routes.assistant._resolvers import _resolve_provider_id
db = MagicMock()
cm = MagicMock()
cm.get_config.side_effect = RuntimeError("Config error")
svc = MagicMock()
p1 = MagicMock(id="p1", name="Provider", is_active=True)
svc.get_all_providers.return_value = [p1]
with patch("src.api.routes.assistant._resolvers.LLMProviderService", return_value=svc):
result = _resolve_provider_id(None, db, config_manager=cm, task_key="x")
assert result == "p1" # falls through to active
class TestGetDefaultEnvironmentId:
"""_get_default_environment_id — default env resolution."""
def test_no_environments(self):
from src.api.routes.assistant._resolvers import _get_default_environment_id
cm = MagicMock()
cm.get_environments.return_value = []
assert _get_default_environment_id(cm) is None
def test_uses_config_default(self):
from src.api.routes.assistant._resolvers import _get_default_environment_id
cm = MagicMock()
envs = [_make_env("env-dev", "Development"), _make_env("env-prod", "Production")]
cm.get_environments.return_value = envs
cm.get_config.return_value.settings.default_environment_id = "env-dev"
assert _get_default_environment_id(cm) == "env-dev"
def test_config_default_not_in_list(self):
from src.api.routes.assistant._resolvers import _get_default_environment_id
cm = MagicMock()
envs = [_make_env("env-dev", "Development")]
cm.get_environments.return_value = envs
cm.get_config.return_value.settings.default_environment_id = "missing"
# Falls through to is_default flag or first
assert _get_default_environment_id(cm) == "env-dev"
def test_uses_is_default_flag(self):
from src.api.routes.assistant._resolvers import _get_default_environment_id
cm = MagicMock()
envs = [
_make_env("env-a", "First"),
_make_env("env-b", "Default", is_default=True),
]
cm.get_environments.return_value = envs
cm.get_config.return_value.settings.default_environment_id = None
assert _get_default_environment_id(cm) == "env-b"
def test_falls_back_to_first(self):
from src.api.routes.assistant._resolvers import _get_default_environment_id
cm = MagicMock()
envs = [_make_env("env-first", "First"), _make_env("env-second", "Second")]
cm.get_environments.return_value = envs
cm.get_config.return_value = None
assert _get_default_environment_id(cm) == "env-first"
def test_config_manager_no_get_config_attr(self):
from src.api.routes.assistant._resolvers import _get_default_environment_id
cm = MagicMock()
cm.get_environments.return_value = [_make_env("env-dev", "Dev")]
# Remove get_config to simulate missing method
if hasattr(cm, 'get_config'):
del cm.get_config
assert _get_default_environment_id(cm) == "env-dev"
class TestGetEnvironmentNameById:
"""_get_environment_name_by_id — human-readable name."""
def test_found(self):
from src.api.routes.assistant._resolvers import _get_environment_name_by_id
cm = MagicMock()
cm.get_environments.return_value = [_make_env("env-dev", "Development")]
assert _get_environment_name_by_id("env-dev", cm) == "Development"
def test_not_found_returns_id(self):
from src.api.routes.assistant._resolvers import _get_environment_name_by_id
cm = MagicMock()
cm.get_environments.return_value = [_make_env("env-dev", "Development")]
assert _get_environment_name_by_id("missing", cm) == "missing"
def test_none_returns_unknown(self):
from src.api.routes.assistant._resolvers import _get_environment_name_by_id
assert _get_environment_name_by_id(None, MagicMock()) == "unknown"
class TestResolveDashboardIdByRef:
"""_resolve_dashboard_id_by_ref — dashboard resolution by slug/title."""
@pytest.mark.asyncio
async def test_none_ref(self):
from src.api.routes.assistant._resolvers import _resolve_dashboard_id_by_ref
assert await _resolve_dashboard_id_by_ref(None, "env-id", MagicMock()) is None
@pytest.mark.asyncio
async def test_no_env_id(self):
from src.api.routes.assistant._resolvers import _resolve_dashboard_id_by_ref
assert await _resolve_dashboard_id_by_ref("my-dash", None, MagicMock()) is None
@pytest.mark.asyncio
async def test_env_not_found(self):
from src.api.routes.assistant._resolvers import _resolve_dashboard_id_by_ref
cm = MagicMock()
cm.get_environments.return_value = [_make_env("other-env", "Other")]
assert await _resolve_dashboard_id_by_ref("my-dash", "env-dev", cm) is None
@pytest.mark.asyncio
async def test_matches_by_slug(self):
from src.api.routes.assistant._resolvers import _resolve_dashboard_id_by_ref
cm = MagicMock()
cm.get_environments.return_value = [_make_env("env-dev", "Dev")]
with patch("src.api.routes.assistant._resolvers.SupersetClient") as mock_cls:
client = MagicMock()
mock_cls.return_value = client
client.get_dashboards = AsyncMock(return_value=(None, [
{"id": 42, "slug": "my-dash", "dashboard_title": "My Dashboard"},
{"id": 99, "slug": "other", "dashboard_title": "Other"},
]))
result = await _resolve_dashboard_id_by_ref("my-dash", "env-dev", cm)
assert result == 42
@pytest.mark.asyncio
async def test_matches_by_title_exact(self):
from src.api.routes.assistant._resolvers import _resolve_dashboard_id_by_ref
cm = MagicMock()
cm.get_environments.return_value = [_make_env("env-dev", "Dev")]
with patch("src.api.routes.assistant._resolvers.SupersetClient") as mock_cls:
client = MagicMock()
mock_cls.return_value = client
client.get_dashboards = AsyncMock(return_value=(None, [
{"id": 7, "slug": None, "dashboard_title": "Sales Dashboard"},
]))
result = await _resolve_dashboard_id_by_ref("Sales Dashboard", "env-dev", cm)
assert result == 7
@pytest.mark.asyncio
async def test_partial_title_match_unique(self):
from src.api.routes.assistant._resolvers import _resolve_dashboard_id_by_ref
cm = MagicMock()
cm.get_environments.return_value = [_make_env("env-dev", "Dev")]
with patch("src.api.routes.assistant._resolvers.SupersetClient") as mock_cls:
client = MagicMock()
mock_cls.return_value = client
client.get_dashboards = AsyncMock(return_value=(None, [
{"id": 15, "slug": None, "dashboard_title": "Customer Analytics"},
{"id": 20, "slug": None, "dashboard_title": "Finance Report"},
]))
result = await _resolve_dashboard_id_by_ref("Analytics", "env-dev", cm)
assert result == 15
@pytest.mark.asyncio
async def test_partial_title_match_ambiguous_returns_none(self):
from src.api.routes.assistant._resolvers import _resolve_dashboard_id_by_ref
cm = MagicMock()
cm.get_environments.return_value = [_make_env("env-dev", "Dev")]
with patch("src.api.routes.assistant._resolvers.SupersetClient") as mock_cls:
client = MagicMock()
mock_cls.return_value = client
client.get_dashboards = AsyncMock(return_value=(None, [
{"id": 1, "slug": None, "dashboard_title": "Dashboard A"},
{"id": 2, "slug": None, "dashboard_title": "Dashboard B"},
]))
result = await _resolve_dashboard_id_by_ref("Dashboard", "env-dev", cm)
assert result is None
@pytest.mark.asyncio
async def test_superset_error_returns_none(self):
from src.api.routes.assistant._resolvers import _resolve_dashboard_id_by_ref
cm = MagicMock()
cm.get_environments.return_value = [_make_env("env-dev", "Dev")]
with patch("src.api.routes.assistant._resolvers.SupersetClient") as mock_cls:
client = MagicMock()
mock_cls.return_value = client
client.get_dashboards = AsyncMock(side_effect=ConnectionError("API down"))
result = await _resolve_dashboard_id_by_ref("my-dash", "env-dev", cm)
assert result is None
class TestResolveDashboardIdEntity:
"""_resolve_dashboard_id_entity — entity resolution."""
@pytest.mark.asyncio
async def test_int_dashboard_id(self):
from src.api.routes.assistant._resolvers import _resolve_dashboard_id_entity
result = await _resolve_dashboard_id_entity({"dashboard_id": 42}, MagicMock())
assert result == 42
@pytest.mark.asyncio
async def test_string_digit_dashboard_id(self):
from src.api.routes.assistant._resolvers import _resolve_dashboard_id_entity
result = await _resolve_dashboard_id_entity({"dashboard_id": "99"}, MagicMock())
assert result == 99
@pytest.mark.asyncio
async def test_string_non_digit_becomes_ref(self):
from src.api.routes.assistant._resolvers import _resolve_dashboard_id_entity
cm = MagicMock()
cm.get_environments.return_value = [_make_env("env-dev", "Dev")]
with patch("src.api.routes.assistant._resolvers._resolve_dashboard_id_by_ref",
new=AsyncMock(return_value=42)):
result = await _resolve_dashboard_id_entity(
{"dashboard_id": "my-slug"}, cm, env_hint="dev"
)
assert result == 42
@pytest.mark.asyncio
async def test_explicit_ref_alone(self):
from src.api.routes.assistant._resolvers import _resolve_dashboard_id_entity
cm = MagicMock()
cm.get_environments.return_value = [_make_env("env-dev", "Dev")]
with patch("src.api.routes.assistant._resolvers._resolve_dashboard_id_by_ref",
new=AsyncMock(return_value=77)):
result = await _resolve_dashboard_id_entity(
{"dashboard_ref": "my-dash"}, cm
)
assert result == 77
@pytest.mark.asyncio
async def test_no_identifiers_returns_none(self):
from src.api.routes.assistant._resolvers import _resolve_dashboard_id_entity
result = await _resolve_dashboard_id_entity({}, MagicMock())
assert result is None
@pytest.mark.asyncio
async def test_env_hint_overrides_entity_env(self):
from src.api.routes.assistant._resolvers import _resolve_dashboard_id_entity
cm = MagicMock()
cm.get_environments.return_value = [_make_env("env-prod", "Prod")]
with patch("src.api.routes.assistant._resolvers._resolve_dashboard_id_by_ref",
new=AsyncMock(return_value=42)):
result = await _resolve_dashboard_id_entity(
{"dashboard_ref": "sales", "environment": "env-dev"},
cm, env_hint="env-prod",
)
assert result == 42
class TestExtractResultDeepLinks:
"""_extract_result_deep_links — deep-link actions from task."""
def test_migration_plugin(self):
from src.api.routes.assistant._resolvers import _extract_result_deep_links
cm = MagicMock()
cm.get_environments.return_value = [_make_env("tgt-env", "Target")]
task = MagicMock()
task.plugin_id = "superset-migration"
task.params = {"target_env_id": "tgt-env", "selected_ids": [42]}
task.result = {"migrated_dashboards": [{"id": 42}]}
actions = _extract_result_deep_links(task, cm)
assert len(actions) >= 2 # open_route + open_diff
assert any(a.type == "open_route" for a in actions)
assert any(a.type == "open_diff" for a in actions)
def test_migration_plugin_result_has_no_migrated_dashboards(self):
from src.api.routes.assistant._resolvers import _extract_result_deep_links
cm = MagicMock()
task = MagicMock()
task.plugin_id = "superset-migration"
task.params = {"target_env_id": "env-prod", "selected_ids": [99]}
task.result = {} # no migrated_dashboards key
actions = _extract_result_deep_links(task, cm)
# Falls back to params.selected_ids
assert len(actions) >= 1 # open_diff with id=99
def test_backup_plugin(self):
from src.api.routes.assistant._resolvers import _extract_result_deep_links
cm = MagicMock()
cm.get_environments.return_value = [_make_env("env-dev", "Development")]
task = MagicMock()
task.plugin_id = "superset-backup"
task.params = {"environment_id": "env-dev", "dashboard_ids": [7]}
task.result = {"dashboards": [{"id": 7}], "environment": "env-dev"}
actions = _extract_result_deep_links(task, cm)
assert len(actions) >= 2
def test_validation_plugin_without_env(self):
from src.api.routes.assistant._resolvers import _extract_result_deep_links
cm = MagicMock()
task = MagicMock()
task.plugin_id = "llm_dashboard_validation"
task.params = {"dashboard_id": 42} # no environment_id
task.result = {}
actions = _extract_result_deep_links(task, cm)
assert len(actions) == 1 # open_diff only (no env -> no open_route)
assert actions[0].type == "open_diff"
def test_validation_plugin_with_env(self):
from src.api.routes.assistant._resolvers import _extract_result_deep_links
cm = MagicMock()
cm.get_environments.return_value = [_make_env("env-dev", "Development")]
task = MagicMock()
task.plugin_id = "llm_dashboard_validation"
task.params = {"dashboard_id": 42, "environment_id": "env-dev"}
task.result = {}
actions = _extract_result_deep_links(task, cm)
assert len(actions) == 2 # open_route + open_diff
def test_unknown_plugin_no_actions(self):
from src.api.routes.assistant._resolvers import _extract_result_deep_links
cm = MagicMock()
task = MagicMock()
task.plugin_id = "some_unknown"
task.params = {}
task.result = {}
actions = _extract_result_deep_links(task, cm)
assert actions == []
class TestBuildTaskObservabilitySummary:
"""_build_task_observability_summary — task summary for different plugins."""
def test_migration_success(self):
from src.api.routes.assistant._resolvers import _build_task_observability_summary
cm = MagicMock()
cm.get_environments.return_value = [_make_env("tgt", "Target")]
task = MagicMock()
task.plugin_id = "superset-migration"
task.status = "SUCCESS"
task.params = {"target_env_id": "tgt"}
task.result = {
"migrated_dashboards": [{"id": 1}, {"id": 2}],
"failed_dashboards": [],
"selected_dashboards": 2,
"mapping_count": 5,
}
summary = _build_task_observability_summary(task, cm)
assert "Сводка миграции" in summary
assert "перенесено 2" in summary
assert "маппингов 5" in summary
def test_migration_with_failures(self):
from src.api.routes.assistant._resolvers import _build_task_observability_summary
cm = MagicMock()
cm.get_environments.return_value = [_make_env("tgt", "Target")]
task = MagicMock()
task.plugin_id = "superset-migration"
task.status = "PARTIAL"
task.params = {"target_env_id": "tgt"}
task.result = {
"migrated_dashboards": [{"id": 1}],
"failed_dashboards": [{"id": 2, "title": "Broken", "error": "timeout"}],
"selected_dashboards": 2,
"mapping_count": 3,
}
summary = _build_task_observability_summary(task, cm)
assert "Сводка миграции" in summary
assert "с ошибками 1" in summary
assert "Broken" in summary
def test_migration_empty_lists(self):
from src.api.routes.assistant._resolvers import _build_task_observability_summary
cm = MagicMock()
cm.get_environments.return_value = [_make_env("tgt", "Target")]
task = MagicMock()
task.plugin_id = "superset-migration"
task.status = "SUCCESS"
task.params = {"target_env_id": "tgt"}
task.result = {
"migrated_dashboards": [],
"failed_dashboards": [],
"mapping_count": 0,
}
summary = _build_task_observability_summary(task, cm)
assert "Сводка миграции" in summary
assert "перенесено 0" in summary
def test_backup_success(self):
from src.api.routes.assistant._resolvers import _build_task_observability_summary
cm = MagicMock()
cm.get_environments.return_value = [_make_env("env-dev", "Development")]
task = MagicMock()
task.plugin_id = "superset-backup"
task.status = "SUCCESS"
task.params = {"environment_id": "env-dev"}
task.result = {
"total_dashboards": 10,
"backed_up_dashboards": 10,
"failed_dashboards": 0,
"failures": [],
}
summary = _build_task_observability_summary(task, cm)
assert "Сводка бэкапа" in summary
assert "успешно 10" in summary
assert "среда" in summary.lower()
def test_backup_with_failures(self):
from src.api.routes.assistant._resolvers import _build_task_observability_summary
cm = MagicMock()
cm.get_environments.return_value = [_make_env("env-dev", "Development")]
task = MagicMock()
task.plugin_id = "superset-backup"
task.status = "PARTIAL"
task.params = {"environment_id": "env-dev"}
task.result = {
"total_dashboards": 5,
"backed_up_dashboards": 3,
"failed_dashboards": 2,
"failures": [{"id": 4, "title": "Dashboard X", "error": "API error"}],
}
summary = _build_task_observability_summary(task, cm)
assert "с ошибками 2" in summary
assert "Dashboard X" in summary
def test_validation_summary(self):
from src.api.routes.assistant._resolvers import _build_task_observability_summary
cm = MagicMock()
task = MagicMock()
task.plugin_id = "llm_dashboard_validation"
task.status = "SUCCESS"
task.params = {}
task.result = {
"status": "passed",
"summary": "All checks passed",
"issues": [],
}
summary = _build_task_observability_summary(task, cm)
assert "Сводка валидации" in summary
assert "статус passed" in summary
def test_validation_result_without_summary(self):
from src.api.routes.assistant._resolvers import _build_task_observability_summary
cm = MagicMock()
task = MagicMock()
task.plugin_id = "llm_dashboard_validation"
task.status = "FAILED"
task.params = {}
task.result = {} # no status or summary
summary = _build_task_observability_summary(task, cm)
assert "Сводка валидации" in summary
assert "Итог недоступен" in summary
def test_unknown_plugin_terminal_status(self):
from src.api.routes.assistant._resolvers import _build_task_observability_summary
cm = MagicMock()
task = MagicMock()
task.plugin_id = "custom-plugin"
task.status = "SUCCESS"
task.params = {}
task.result = {}
summary = _build_task_observability_summary(task, cm)
assert "Задача завершена" in summary
def test_unknown_plugin_non_terminal_status(self):
from src.api.routes.assistant._resolvers import _build_task_observability_summary
cm = MagicMock()
task = MagicMock()
task.plugin_id = "custom-plugin"
task.status = "RUNNING"
task.params = {}
task.result = {}
summary = _build_task_observability_summary(task, cm)
assert summary == ""
# #endregion Test.Assistant.Resolvers