# #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