Files
ss-tools/backend/tests/api/test_dashboard_helpers.py
busya ce0369ae5c test(backend): add 55+ test files to push coverage to 98%
Subagents delivered tests across all uncovered backend modules:

Schemas (100%): agent, auth, health, profile, settings, validation
Services (98-100%): auth, profile, health, llm, mapping, resource,
  security, git, superset_lookup, sql_table_extractor, rbac
API routes (new): auth, admin, health, environments, plugins,
  dashboards (helpers, projection, actions, listing),
  git (config, deps, env, helpers)
Clean Release (100%): DTO, facade, policy_engine, stages,
  repos, preparation, source_isolation, compliance
Git services: base, remote_providers
Agent module: app, run, middleware, langgraph_setup
Core: trace, cleanup, ws_log_handler, timezone, auth (config/oauth/security), matching
Reports: normalizer, report_service, type_profiles
Notifications: service, providers

Also:
- .gitignore: add .coverage, *.cover, coverage-* dirs
- src/schemas/auth.py: fix AD group DN regex (comma in CN=...)
- Remove co-located src/services/__tests__/ (caused pytest module collision)
2026-06-15 13:55:57 +03:00

194 lines
7.7 KiB
Python

# #region Test.Api.DashboardHelpers [C:3] [TYPE Module] [SEMANTICS test,dashboard,helpers]
# @BRIEF Unit tests for dashboard helper functions.
# @RELATION BINDS_TO -> [DashboardHelpers]
# @TEST_EDGE: empty_ref -> 404
# @TEST_EDGE: slug_resolution -> int | None
# @TEST_EDGE: numeric_ref_fallback -> int
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)
# ── Deprecated sync functions ──
class TestDeprecatedSyncFunctions:
"""_find_dashboard_id_by_slug and _resolve_dashboard_id_from_ref (sync) raise RuntimeError."""
def test_find_dashboard_id_by_slug_deprecated(self):
from src.api.routes.dashboards._helpers import _find_dashboard_id_by_slug
with pytest.raises(RuntimeError, match="deprecated"):
_find_dashboard_id_by_slug(MagicMock(), "test-slug")
def test_resolve_dashboard_id_from_ref_multiple_defs(self):
"""The module has two defs of _resolve_dashboard_id_from_ref. The second wins.
It should raise HTTPException(404) for unknown refs, not RuntimeError."""
from src.api.routes.dashboards._helpers import _resolve_dashboard_id_from_ref
from fastapi import HTTPException as HE
client = MagicMock()
client.get_dashboards_page.return_value = (0, [])
with pytest.raises(HE) as exc:
_resolve_dashboard_id_from_ref("unknown-ref", client)
assert exc.value.status_code == 404
# ── _find_dashboard_id_by_slug_async ──
class TestFindDashboardIdBySlugAsync:
"""_find_dashboard_id_by_slug_async"""
@pytest.mark.asyncio
async def test_found_by_slug(self):
client = AsyncMock()
client.get_dashboards_page.return_value = (1, [{"id": 42}])
from src.api.routes.dashboards._helpers import _find_dashboard_id_by_slug_async
result = await _find_dashboard_id_by_slug_async(client, "my-slug")
assert result == 42
@pytest.mark.asyncio
async def test_not_found(self):
client = AsyncMock()
client.get_dashboards_page.return_value = (0, [])
from src.api.routes.dashboards._helpers import _find_dashboard_id_by_slug_async
result = await _find_dashboard_id_by_slug_async(client, "ghost-slug")
assert result is None
@pytest.mark.asyncio
async def test_exception_then_second_query(self):
client = AsyncMock()
# First query raises, second succeeds
client.get_dashboards_page.side_effect = [
Exception("First query failed"),
(1, [{"id": 99}]),
]
from src.api.routes.dashboards._helpers import _find_dashboard_id_by_slug_async
result = await _find_dashboard_id_by_slug_async(client, "retry-slug")
assert result == 99
@pytest.mark.asyncio
async def test_all_queries_fail(self):
client = AsyncMock()
client.get_dashboards_page.side_effect = Exception("All failed")
from src.api.routes.dashboards._helpers import _find_dashboard_id_by_slug_async
result = await _find_dashboard_id_by_slug_async(client, "fail-slug")
assert result is None
# ── _resolve_dashboard_id_from_ref_async ──
class TestResolveDashboardIdFromRefAsync:
"""_resolve_dashboard_id_from_ref_async"""
@pytest.mark.asyncio
async def test_numeric_ref(self):
from src.api.routes.dashboards._helpers import _resolve_dashboard_id_from_ref_async
result = await _resolve_dashboard_id_from_ref_async("123", AsyncMock())
assert result == 123
@pytest.mark.asyncio
async def test_slug_ref(self):
from src.api.routes.dashboards._helpers import _resolve_dashboard_id_from_ref_async
client = AsyncMock()
client.get_dashboards_page.return_value = (1, [{"id": 42}])
result = await _resolve_dashboard_id_from_ref_async("my-dash", client)
assert result == 42
@pytest.mark.asyncio
async def test_empty_ref(self):
from fastapi import HTTPException
from src.api.routes.dashboards._helpers import _resolve_dashboard_id_from_ref_async
with pytest.raises(HTTPException) as exc:
await _resolve_dashboard_id_from_ref_async("", AsyncMock())
assert exc.value.status_code == 404
@pytest.mark.asyncio
async def test_slug_not_found(self):
from fastapi import HTTPException
from src.api.routes.dashboards._helpers import _resolve_dashboard_id_from_ref_async
client = AsyncMock()
client.get_dashboards_page.return_value = (0, [])
with pytest.raises(HTTPException) as exc:
await _resolve_dashboard_id_from_ref_async("ghost", client)
assert exc.value.status_code == 404
@pytest.mark.asyncio
async def test_none_ref(self):
from fastapi import HTTPException
from src.api.routes.dashboards._helpers import _resolve_dashboard_id_from_ref_async
with pytest.raises(HTTPException) as exc:
await _resolve_dashboard_id_from_ref_async(None, AsyncMock()) # type: ignore
assert exc.value.status_code == 404
# ── _normalize_filter_values ──
class TestNormalizeFilterValues:
"""_normalize_filter_values"""
def test_normalize_returns_lowercase(self):
from src.api.routes.dashboards._helpers import _normalize_filter_values
assert _normalize_filter_values(["HELLO", "World"]) == ["hello", "world"]
def test_normalize_empty_list(self):
from src.api.routes.dashboards._helpers import _normalize_filter_values
assert _normalize_filter_values([]) == []
def test_normalize_none(self):
from src.api.routes.dashboards._helpers import _normalize_filter_values
assert _normalize_filter_values(None) == []
def test_normalize_removes_empty_strings(self):
from src.api.routes.dashboards._helpers import _normalize_filter_values
assert _normalize_filter_values(["a", "", " ", "b"]) == ["a", "b"]
def test_normalize_strips_whitespace(self):
from src.api.routes.dashboards._helpers import _normalize_filter_values
assert _normalize_filter_values([" Foo Bar "]) == ["foo bar"]
# ── _dashboard_git_filter_value ──
class TestDashboardGitFilterValue:
"""_dashboard_git_filter_value"""
def test_no_repo(self):
from src.api.routes.dashboards._helpers import _dashboard_git_filter_value
assert _dashboard_git_filter_value({"git_status": {"has_repo": False}}) == "no_repo"
def test_no_repo_status(self):
from src.api.routes.dashboards._helpers import _dashboard_git_filter_value
assert _dashboard_git_filter_value({"git_status": {"sync_status": "NO_REPO"}}) == "no_repo"
def test_diff(self):
from src.api.routes.dashboards._helpers import _dashboard_git_filter_value
assert _dashboard_git_filter_value({"git_status": {"sync_status": "DIFF", "has_repo": True}}) == "diff"
def test_ok(self):
from src.api.routes.dashboards._helpers import _dashboard_git_filter_value
assert _dashboard_git_filter_value({"git_status": {"sync_status": "OK", "has_repo": True}}) == "ok"
def test_error(self):
from src.api.routes.dashboards._helpers import _dashboard_git_filter_value
assert _dashboard_git_filter_value({"git_status": {"sync_status": "ERROR", "has_repo": True}}) == "error"
def test_missing_git_status(self):
from src.api.routes.dashboards._helpers import _dashboard_git_filter_value
assert _dashboard_git_filter_value({}) == "pending"
def test_none_git_status(self):
from src.api.routes.dashboards._helpers import _dashboard_git_filter_value
assert _dashboard_git_filter_value({"git_status": None}) == "pending"
# #endregion Test.Api.DashboardHelpers