- 10 translate plugin test files (100% coverage on 12 modules) - assistant/handler tools: 85+ tests covering dispatch, registry, resolvers, routes, llm_planner, 13 tool handlers - clean release: artifact_catalog_loader, mappers, approval, publication tests - API routes: translate_helpers, validation_service extensions, datasets to 100% - notifications: providers/service tests - services: profile_preference_service - docs/orthogonal-test-report.md — full speckit.tests audit - Fixes: 3 git_base async mock failures, 4 assistant handler permission-check patches - .gitignore: coverage artifacts
280 lines
12 KiB
Python
280 lines
12 KiB
Python
# #region Test.Api.DashboardProjection [C:3] [TYPE Module] [SEMANTICS test,dashboard,projection]
|
|
# @BRIEF Unit tests for dashboard projection/profile helpers.
|
|
# @RELATION BINDS_TO -> [DashboardProjection]
|
|
# @TEST_EDGE: owner_normalization
|
|
# @TEST_EDGE: profile_filter_binding
|
|
# @TEST_EDGE: task_matching
|
|
|
|
import sys
|
|
from pathlib import Path
|
|
from unittest.mock import AsyncMock, MagicMock, patch
|
|
|
|
import pytest
|
|
|
|
_src = str(Path(__file__).resolve().parent.parent.parent / "src")
|
|
if _src not in sys.path:
|
|
sys.path.insert(0, _src)
|
|
|
|
|
|
# ── _normalize_actor_alias_token ──
|
|
|
|
class TestNormalizeActorAliasToken:
|
|
def test_none_value(self):
|
|
from src.api.routes.dashboards._projection import _normalize_actor_alias_token
|
|
assert _normalize_actor_alias_token(None) is None
|
|
|
|
def test_empty_string(self):
|
|
from src.api.routes.dashboards._projection import _normalize_actor_alias_token
|
|
assert _normalize_actor_alias_token("") is None
|
|
|
|
def test_whitespace(self):
|
|
from src.api.routes.dashboards._projection import _normalize_actor_alias_token
|
|
assert _normalize_actor_alias_token(" ") is None
|
|
|
|
def test_normalized(self):
|
|
from src.api.routes.dashboards._projection import _normalize_actor_alias_token
|
|
assert _normalize_actor_alias_token(" Alice ") == "alice"
|
|
|
|
|
|
# ── _normalize_owner_display_token ──
|
|
|
|
class TestNormalizeOwnerDisplayToken:
|
|
def test_none(self):
|
|
from src.api.routes.dashboards._projection import _normalize_owner_display_token
|
|
assert _normalize_owner_display_token(None) is None
|
|
|
|
def test_dict_with_username(self):
|
|
from src.api.routes.dashboards._projection import _normalize_owner_display_token
|
|
assert _normalize_owner_display_token({"username": "Alice"}) == "Alice"
|
|
|
|
def test_dict_with_full_name(self):
|
|
from src.api.routes.dashboards._projection import _normalize_owner_display_token
|
|
assert _normalize_owner_display_token({"full_name": "Alice Smith"}) == "Alice Smith"
|
|
|
|
def test_dict_empty(self):
|
|
from src.api.routes.dashboards._projection import _normalize_owner_display_token
|
|
assert _normalize_owner_display_token({}) is None
|
|
|
|
def test_string(self):
|
|
from src.api.routes.dashboards._projection import _normalize_owner_display_token
|
|
assert _normalize_owner_display_token("Alice") == "Alice"
|
|
|
|
def test_int(self):
|
|
from src.api.routes.dashboards._projection import _normalize_owner_display_token
|
|
assert _normalize_owner_display_token(42) is None
|
|
|
|
|
|
# ── _normalize_dashboard_owner_values ──
|
|
|
|
class TestNormalizeDashboardOwnerValues:
|
|
def test_none(self):
|
|
from src.api.routes.dashboards._projection import _normalize_dashboard_owner_values
|
|
assert _normalize_dashboard_owner_values(None) is None
|
|
|
|
def test_list_of_dicts(self):
|
|
from src.api.routes.dashboards._projection import _normalize_dashboard_owner_values
|
|
result = _normalize_dashboard_owner_values([
|
|
{"username": "Alice"},
|
|
{"username": "Bob"},
|
|
])
|
|
assert result == ["Alice", "Bob"]
|
|
|
|
def test_single_dict(self):
|
|
from src.api.routes.dashboards._projection import _normalize_dashboard_owner_values
|
|
result = _normalize_dashboard_owner_values({"username": "Charlie"})
|
|
assert result == ["Charlie"]
|
|
|
|
def test_duplicates_removed(self):
|
|
from src.api.routes.dashboards._projection import _normalize_dashboard_owner_values
|
|
result = _normalize_dashboard_owner_values([
|
|
{"username": "Alice"},
|
|
{"username": "Alice"},
|
|
])
|
|
assert result == ["Alice"]
|
|
|
|
|
|
# ── _project_dashboard_response_items ──
|
|
|
|
class TestProjectDashboardResponseItems:
|
|
def test_owners_normalized(self):
|
|
from src.api.routes.dashboards._projection import _project_dashboard_response_items
|
|
dashboards = [
|
|
{"id": 1, "title": "Main", "owners": [{"username": "Alice"}]},
|
|
]
|
|
result = _project_dashboard_response_items(dashboards)
|
|
assert result[0]["owners"] == ["Alice"]
|
|
|
|
def test_empty_list(self):
|
|
from src.api.routes.dashboards._projection import _project_dashboard_response_items
|
|
assert _project_dashboard_response_items([]) == []
|
|
|
|
|
|
# ── _get_profile_filter_binding ──
|
|
|
|
class TestGetProfileFilterBinding:
|
|
def test_uses_get_dashboard_filter_binding(self):
|
|
from src.api.routes.dashboards._projection import _get_profile_filter_binding
|
|
profile_service = MagicMock()
|
|
profile_service.get_dashboard_filter_binding.return_value = {
|
|
"superset_username": "alice",
|
|
"superset_username_normalized": "alice",
|
|
"show_only_my_dashboards": True,
|
|
"show_only_slug_dashboards": False,
|
|
}
|
|
result = _get_profile_filter_binding(profile_service, MagicMock())
|
|
assert result["superset_username"] == "alice"
|
|
assert result["show_only_my_dashboards"] is True
|
|
|
|
def test_uses_get_my_preference_fallback(self):
|
|
from src.api.routes.dashboards._projection import _get_profile_filter_binding
|
|
profile_service = MagicMock()
|
|
# Remove the attribute so hasattr returns False, triggering fallback to get_my_preference
|
|
del profile_service.get_dashboard_filter_binding
|
|
pref = MagicMock()
|
|
pref.preference.superset_username = "bob"
|
|
pref.preference.superset_username_normalized = "bob"
|
|
pref.preference.show_only_my_dashboards = True
|
|
pref.preference.show_only_slug_dashboards = False
|
|
profile_service.get_my_preference.return_value = pref
|
|
|
|
result = _get_profile_filter_binding(profile_service, MagicMock())
|
|
assert result["superset_username"] == "bob"
|
|
|
|
def test_no_methods_returns_defaults(self):
|
|
from src.api.routes.dashboards._projection import _get_profile_filter_binding
|
|
profile_service = MagicMock(spec=[]) # no relevant methods
|
|
result = _get_profile_filter_binding(profile_service, MagicMock())
|
|
assert result["superset_username"] is None
|
|
assert result["show_only_my_dashboards"] is False
|
|
|
|
|
|
# ── _resolve_profile_actor_aliases ──
|
|
|
|
class TestResolveProfileActorAliases:
|
|
@pytest.mark.asyncio
|
|
async def test_empty_username(self):
|
|
from src.api.routes.dashboards._projection import _resolve_profile_actor_aliases
|
|
result = await _resolve_profile_actor_aliases(MagicMock(), "")
|
|
assert result == []
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_lookup_success(self):
|
|
from src.api.routes.dashboards._projection import _resolve_profile_actor_aliases
|
|
|
|
mock_adapter = AsyncMock()
|
|
mock_adapter.get_users_page.return_value = {
|
|
"items": [
|
|
{"username": "alice", "display_name": "Alice Smith"},
|
|
]
|
|
}
|
|
|
|
mock_env = MagicMock()
|
|
mock_env.id = "env-1"
|
|
|
|
with patch("src.api.routes.dashboards._projection.AsyncSupersetClient") as MockClient, \
|
|
patch("src.api.routes.dashboards._projection.SupersetAccountLookupAdapter") as MockAdapter:
|
|
|
|
MockAdapter.return_value = mock_adapter
|
|
|
|
result = await _resolve_profile_actor_aliases(mock_env, "alice")
|
|
assert "alice" in result
|
|
assert "alice smith" in result
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_lookup_exception(self):
|
|
from src.api.routes.dashboards._projection import _resolve_profile_actor_aliases
|
|
|
|
mock_env = MagicMock()
|
|
mock_env.id = "env-1"
|
|
|
|
with patch("src.api.routes.dashboards._projection.AsyncSupersetClient") as MockClient, \
|
|
patch("src.api.routes.dashboards._projection.SupersetAccountLookupAdapter") as MockAdapter:
|
|
|
|
MockAdapter.side_effect = Exception("Connection error")
|
|
|
|
result = await _resolve_profile_actor_aliases(mock_env, "alice")
|
|
assert result == ["alice"]
|
|
|
|
|
|
# ── _matches_dashboard_actor_aliases ──
|
|
|
|
class TestMatchesDashboardActorAliases:
|
|
def test_matches(self):
|
|
from src.api.routes.dashboards._projection import _matches_dashboard_actor_aliases
|
|
profile_service = MagicMock()
|
|
profile_service.matches_dashboard_actor.return_value = True
|
|
assert _matches_dashboard_actor_aliases(profile_service, ["alice"], [], "bob") is True
|
|
|
|
def test_no_match(self):
|
|
from src.api.routes.dashboards._projection import _matches_dashboard_actor_aliases
|
|
profile_service = MagicMock()
|
|
profile_service.matches_dashboard_actor.return_value = False
|
|
assert _matches_dashboard_actor_aliases(profile_service, ["alice"], [], None) is False
|
|
|
|
def test_first_match_short_circuits(self):
|
|
from src.api.routes.dashboards._projection import _matches_dashboard_actor_aliases
|
|
profile_service = MagicMock()
|
|
profile_service.matches_dashboard_actor.side_effect = [False, True]
|
|
assert _matches_dashboard_actor_aliases(profile_service, ["alice", "bob"], [], None) is True
|
|
assert profile_service.matches_dashboard_actor.call_count == 2
|
|
|
|
|
|
# ── _task_matches_dashboard ──
|
|
|
|
class TestTaskMatchesDashboard:
|
|
def test_llm_validation_matches(self):
|
|
from src.api.routes.dashboards._projection import _task_matches_dashboard
|
|
task = MagicMock()
|
|
task.plugin_id = "llm_dashboard_validation"
|
|
task.params = {"dashboard_id": 42, "environment_id": "env-1"}
|
|
assert _task_matches_dashboard(task, 42, "env-1") is True
|
|
|
|
def test_llm_validation_wrong_dashboard(self):
|
|
from src.api.routes.dashboards._projection import _task_matches_dashboard
|
|
task = MagicMock()
|
|
task.plugin_id = "llm_dashboard_validation"
|
|
task.params = {"dashboard_id": 99}
|
|
assert _task_matches_dashboard(task, 42, None) is False
|
|
|
|
def test_llm_validation_no_env(self):
|
|
from src.api.routes.dashboards._projection import _task_matches_dashboard
|
|
task = MagicMock()
|
|
task.plugin_id = "llm_dashboard_validation"
|
|
task.params = {"dashboard_id": 42}
|
|
assert _task_matches_dashboard(task, 42, None) is True
|
|
|
|
def test_backup_matches(self):
|
|
from src.api.routes.dashboards._projection import _task_matches_dashboard
|
|
task = MagicMock()
|
|
task.plugin_id = "superset-backup"
|
|
task.params = {"dashboard_ids": [1, 42, 3], "environment_id": "env-1"}
|
|
assert _task_matches_dashboard(task, 42, "env-1") is True
|
|
|
|
def test_backup_dashboards_key(self):
|
|
from src.api.routes.dashboards._projection import _task_matches_dashboard
|
|
task = MagicMock()
|
|
task.plugin_id = "superset-backup"
|
|
task.params = {"dashboards": [42], "env": "env-1"}
|
|
assert _task_matches_dashboard(task, 42, "env-1") is True
|
|
|
|
def test_unrelated_plugin(self):
|
|
from src.api.routes.dashboards._projection import _task_matches_dashboard
|
|
task = MagicMock()
|
|
task.plugin_id = "some-other-plugin"
|
|
assert _task_matches_dashboard(task, 42, "env-1") is False
|
|
|
|
def test_backup_wrong_dashboard(self):
|
|
from src.api.routes.dashboards._projection import _task_matches_dashboard
|
|
task = MagicMock()
|
|
task.plugin_id = "superset-backup"
|
|
task.params = {"dashboard_ids": [1, 2], "environment_id": "env-1"}
|
|
assert _task_matches_dashboard(task, 42, "env-1") is False
|
|
|
|
def test_backup_wrong_env(self):
|
|
from src.api.routes.dashboards._projection import _task_matches_dashboard
|
|
task = MagicMock()
|
|
task.plugin_id = "superset-backup"
|
|
task.params = {"dashboard_ids": [42], "environment_id": "env-1"}
|
|
assert _task_matches_dashboard(task, 42, "env-2") is False
|
|
# #endregion Test.Api.DashboardProjection
|