- 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
192 lines
7.6 KiB
Python
192 lines
7.6 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.
|
|
Both are Tombstone and raise RuntimeError."""
|
|
from src.api.routes.dashboards._helpers import _resolve_dashboard_id_from_ref
|
|
|
|
client = MagicMock()
|
|
client.get_dashboards_page.return_value = (0, [])
|
|
|
|
with pytest.raises(RuntimeError, match="deprecated"):
|
|
_resolve_dashboard_id_from_ref("unknown-ref", client)
|
|
|
|
|
|
# ── _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
|