Files
ss-tools/backend/tests/api/test_git_helpers.py
busya 488a8f349b test(backend): raise coverage to 95%+ statements and branches (97.8%/95.0%)
- ~60 new/extended test files across api, core, plugins, services, schemas:
  routes, superset clients, task_manager, lineage, git, translate,
  dashboard-testing, load-testing, migration, llm_analysis, scheduler, ssl
- .coveragerc: enable branch coverage; exclude src/__tests__ (test files)
  and src/scripts (CLI/ops tools) from the denominator
- bug fixes found while testing:
  * settings: PUT /settings/reports registered under duplicated prefix
  * schemas/lineage: FleetReportDTO missing run_status (route always 500)
  * dashboard_testing/baseline_inheritance: visual entry read wrong field
  * superset_client/_databases: logger extra name shadowed LogRecord attr
  * routes/datasets: _yaml_string_paths recursion without yield from
  * translate/sql_generator: restore explicit-type timestamp contract
  * baseline_catalog: remove unreachable dashboard_id fallback
- conftest fixes: pytest_plugins to rootdir conftest (pytest 9), test
  filename collision, TMPDIR-safe integration fixtures
2026-08-19 17:14:32 +03:00

587 lines
23 KiB
Python

# #region Test.Api.GitHelpers [C:3] [TYPE Module] [SEMANTICS test,git,helpers]
# @BRIEF Unit tests for git helper functions.
# @RELATION BINDS_TO -> [Api.Helpers.GitHelpers]
# @TEST_EDGE: no_repo_path
# @TEST_EDGE: config_not_found -> 404
# @TEST_EDGE: slug_not_found -> 404
# @TEST_EDGE: empty_ref -> 400
import os
import sys
from pathlib import Path
from types import SimpleNamespace
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)
# ── _build_no_repo_status_payload ──
class TestBuildNoRepoStatusPayload:
def test_payload_structure(self):
from src.api.routes.git._helpers import _build_no_repo_status_payload
payload = _build_no_repo_status_payload()
assert payload["sync_status"] == "NO_REPO"
assert payload["has_repo"] is False
assert payload["is_dirty"] is False
assert payload["untracked_files"] == []
# ── _handle_unexpected_git_route_error ──
class TestHandleUnexpectedGitRouteError:
def test_raises_http_500(self):
from fastapi import HTTPException
from src.api.routes.git._helpers import _handle_unexpected_git_route_error
with pytest.raises(HTTPException) as exc:
_handle_unexpected_git_route_error("test_route", RuntimeError("boom"))
assert exc.value.status_code == 500
assert "test_route failed" in exc.value.detail
# ── _resolve_repository_status ──
class TestResolveRepositoryStatus:
@pytest.mark.asyncio
async def test_repo_path_exists(self):
mock_service = MagicMock()
mock_service._get_repo_path = AsyncMock(return_value="/tmp/some-repo")
mock_service.get_status = AsyncMock(return_value={"sync_status": "OK"})
with patch("src.api.routes.git._helpers.get_git_service", return_value=mock_service), \
patch("os.path.exists", return_value=True):
from src.api.routes.git._helpers import _resolve_repository_status
result = await _resolve_repository_status(42)
assert result["sync_status"] == "OK"
@pytest.mark.asyncio
async def test_repo_path_missing(self):
mock_service = MagicMock()
mock_service._get_repo_path = AsyncMock(return_value="/nonexistent")
with patch("src.api.routes.git._helpers.get_git_service", return_value=mock_service), \
patch("os.path.exists", return_value=False):
from src.api.routes.git._helpers import _resolve_repository_status
result = await _resolve_repository_status(42)
assert result["sync_status"] == "NO_REPO"
@pytest.mark.asyncio
async def test_get_status_returns_404(self):
from fastapi import HTTPException
mock_service = MagicMock()
mock_service._get_repo_path = AsyncMock(return_value="/tmp/repo")
mock_service.get_status = AsyncMock(side_effect=HTTPException(status_code=404, detail="No repo"))
with patch("src.api.routes.git._helpers.get_git_service", return_value=mock_service), \
patch("os.path.exists", return_value=True):
from src.api.routes.git._helpers import _resolve_repository_status
result = await _resolve_repository_status(42)
assert result["sync_status"] == "NO_REPO"
@pytest.mark.asyncio
async def test_get_status_raises_other_http(self):
from fastapi import HTTPException
mock_service = MagicMock()
mock_service._get_repo_path = AsyncMock(return_value="/tmp/repo")
mock_service.get_status = AsyncMock(side_effect=HTTPException(status_code=500, detail="Server error"))
with patch("src.api.routes.git._helpers.get_git_service", return_value=mock_service), \
patch("os.path.exists", return_value=True):
from src.api.routes.git._helpers import _resolve_repository_status
with pytest.raises(HTTPException) as exc:
await _resolve_repository_status(42)
assert exc.value.status_code == 500
# ── _get_git_config_or_404 ──
class TestGetGitConfigOr404:
def test_config_found(self):
mock_db = MagicMock()
mock_db.query.return_value.filter.return_value.first.return_value = MagicMock(id="cfg-1")
from src.api.routes.git._helpers import _get_git_config_or_404
result = _get_git_config_or_404(mock_db, "cfg-1")
assert result is not None
def test_config_not_found(self):
from fastapi import HTTPException
mock_db = MagicMock()
mock_db.query.return_value.filter.return_value.first.return_value = None
from src.api.routes.git._helpers import _get_git_config_or_404
with pytest.raises(HTTPException) as exc:
_get_git_config_or_404(mock_db, "cfg-missing")
assert exc.value.status_code == 404
# ── _find_dashboard_id_by_slug (sync) ──
class TestFindDashboardIdBySlug:
@pytest.mark.asyncio
async def test_found(self):
client = AsyncMock()
client.get_dashboards_page.return_value = (1, [{"id": 42}])
from src.api.routes.git._helpers import _find_dashboard_id_by_slug
result = await _find_dashboard_id_by_slug(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.git._helpers import _find_dashboard_id_by_slug
result = await _find_dashboard_id_by_slug(client, "ghost")
assert result is None
@pytest.mark.asyncio
async def test_exception_then_second_query(self):
client = AsyncMock()
client.get_dashboards_page.side_effect = [
Exception("Fail"),
(1, [{"id": 99}]),
]
from src.api.routes.git._helpers import _find_dashboard_id_by_slug
result = await _find_dashboard_id_by_slug(client, "retry")
assert result == 99
# ── _resolve_dashboard_id_from_ref (sync) ──
class TestResolveDashboardIdFromRef:
@pytest.mark.asyncio
async def test_numeric_ref(self):
from src.api.routes.git._helpers import _resolve_dashboard_id_from_ref
result = await _resolve_dashboard_id_from_ref("123", MagicMock())
assert result == 123
@pytest.mark.asyncio
async def test_slug_ref(self):
mock_config = MagicMock()
env = MagicMock()
env.id = "env-1"
mock_config.get_environments.return_value = [env]
client = AsyncMock()
client.get_dashboards_page.return_value = (1, [{"id": 42}])
with patch("src.api.routes.git._helpers.SupersetClient", return_value=client):
from src.api.routes.git._helpers import _resolve_dashboard_id_from_ref
result = await _resolve_dashboard_id_from_ref("my-slug", mock_config, "env-1")
assert result == 42
@pytest.mark.asyncio
async def test_empty_ref(self):
from fastapi import HTTPException
from src.api.routes.git._helpers import _resolve_dashboard_id_from_ref
with pytest.raises(HTTPException) as exc:
await _resolve_dashboard_id_from_ref("", MagicMock())
assert exc.value.status_code == 400
@pytest.mark.asyncio
async def test_slug_no_env_id(self):
from fastapi import HTTPException
from src.api.routes.git._helpers import _resolve_dashboard_id_from_ref
with pytest.raises(HTTPException) as exc:
await _resolve_dashboard_id_from_ref("slug", MagicMock())
assert exc.value.status_code == 400
assert "env_id is required" in exc.value.detail
@pytest.mark.asyncio
async def test_env_not_found(self):
from fastapi import HTTPException
mock_config = MagicMock()
mock_config.get_environments.return_value = []
from src.api.routes.git._helpers import _resolve_dashboard_id_from_ref
with pytest.raises(HTTPException) as exc:
await _resolve_dashboard_id_from_ref("slug", mock_config, "env-ghost")
assert exc.value.status_code == 404
@pytest.mark.asyncio
async def test_slug_not_found(self):
from fastapi import HTTPException
mock_config = MagicMock()
env = MagicMock()
env.id = "env-1"
mock_config.get_environments.return_value = [env]
client = MagicMock()
client.get_dashboards_page.return_value = (0, [])
with patch("src.api.routes.git._helpers.SupersetClient", return_value=client):
from src.api.routes.git._helpers import _resolve_dashboard_id_from_ref
with pytest.raises(HTTPException) as exc:
await _resolve_dashboard_id_from_ref("ghost-slug", mock_config, "env-1")
assert exc.value.status_code == 404
# ── _find_dashboard_id_by_slug_async ──
class TestFindDashboardIdBySlugAsync:
@pytest.mark.asyncio
async def test_found(self):
client = AsyncMock()
client.get_dashboards_page.return_value = (1, [{"id": 42}])
from src.api.routes.git._helpers import _find_dashboard_id_by_slug_async
result = await _find_dashboard_id_by_slug_async(client, "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.git._helpers import _find_dashboard_id_by_slug_async
result = await _find_dashboard_id_by_slug_async(client, "ghost")
assert result is None
# ── _resolve_dashboard_id_from_ref_async ──
class TestResolveDashboardIdFromRefAsync:
@pytest.mark.asyncio
async def test_numeric_ref(self):
from src.api.routes.git._helpers import _resolve_dashboard_id_from_ref_async
result = await _resolve_dashboard_id_from_ref_async("123", MagicMock())
assert result == 123
@pytest.mark.asyncio
async def test_slug_ref(self):
mock_config = MagicMock()
env = MagicMock()
env.id = "env-1"
mock_config.get_environments.return_value = [env]
client = AsyncMock()
client.get_dashboards_page.return_value = (1, [{"id": 42}])
client.aclose = AsyncMock()
with patch("src.core.async_superset_client.AsyncSupersetClient", return_value=client):
from src.api.routes.git._helpers import _resolve_dashboard_id_from_ref_async
result = await _resolve_dashboard_id_from_ref_async("slug", mock_config, "env-1")
assert result == 42
client.aclose.assert_awaited_once()
@pytest.mark.asyncio
async def test_empty_ref(self):
from fastapi import HTTPException
from src.api.routes.git._helpers import _resolve_dashboard_id_from_ref_async
with pytest.raises(HTTPException) as exc:
await _resolve_dashboard_id_from_ref_async("", MagicMock())
assert exc.value.status_code == 400
@pytest.mark.asyncio
async def test_slug_not_found_async(self):
from fastapi import HTTPException
mock_config = MagicMock()
env = MagicMock()
env.id = "env-1"
mock_config.get_environments.return_value = [env]
client = AsyncMock()
client.get_dashboards_page.return_value = (0, [])
client.aclose = AsyncMock()
with patch("src.core.async_superset_client.AsyncSupersetClient", return_value=client):
from src.api.routes.git._helpers import _resolve_dashboard_id_from_ref_async
with pytest.raises(HTTPException) as exc:
await _resolve_dashboard_id_from_ref_async("ghost", mock_config, "env-1")
assert exc.value.status_code == 404
client.aclose.assert_awaited_once()
@pytest.mark.asyncio
async def test_no_env_id(self):
from fastapi import HTTPException
from src.api.routes.git._helpers import _resolve_dashboard_id_from_ref_async
with pytest.raises(HTTPException) as exc:
await _resolve_dashboard_id_from_ref_async("slug", MagicMock())
assert exc.value.status_code == 400
# ── _resolve_repo_key_from_ref ──
class TestResolveRepoKeyFromRef:
@pytest.mark.asyncio
async def test_slug_ref_returns_slug(self):
from src.api.routes.git._helpers import _resolve_repo_key_from_ref
result = await _resolve_repo_key_from_ref("my-dash", 42, MagicMock())
assert result == "my-dash"
@pytest.mark.asyncio
async def test_numeric_ref_returns_dashboard_id_fallback(self):
from src.api.routes.git._helpers import _resolve_repo_key_from_ref
result = await _resolve_repo_key_from_ref("123", 42, MagicMock())
assert result == "dashboard-42"
@pytest.mark.asyncio
async def test_numeric_ref_with_env_lookup(self):
mock_config = MagicMock()
env = MagicMock()
env.id = "env-1"
mock_config.get_environments.return_value = [env]
client = AsyncMock()
client.get_dashboard.return_value = {"result": {"slug": "real-slug"}}
with patch("src.api.routes.git._helpers.SupersetClient", return_value=client):
from src.api.routes.git._helpers import _resolve_repo_key_from_ref
result = await _resolve_repo_key_from_ref("123", 42, mock_config, "env-1")
assert result == "real-slug"
@pytest.mark.asyncio
async def test_env_lookup_fails_uses_fallback(self):
mock_config = MagicMock()
env = MagicMock()
env.id = "env-1"
mock_config.get_environments.return_value = [env]
client = MagicMock()
client.get_dashboard.side_effect = Exception("API error")
with patch("src.api.routes.git._helpers.SupersetClient", return_value=client):
from src.api.routes.git._helpers import _resolve_repo_key_from_ref
result = await _resolve_repo_key_from_ref("123", 42, mock_config, "env-1")
assert result == "dashboard-42"
@pytest.mark.asyncio
async def test_empty_ref_uses_fallback(self):
from src.api.routes.git._helpers import _resolve_repo_key_from_ref
result = await _resolve_repo_key_from_ref("", 42, MagicMock())
assert result == "dashboard-42"
# ── _sanitize_optional_identity_value ──
class TestSanitizeOptionalIdentityValue:
def test_none(self):
from src.api.routes.git._helpers import _sanitize_optional_identity_value
assert _sanitize_optional_identity_value(None) is None
def test_empty(self):
from src.api.routes.git._helpers import _sanitize_optional_identity_value
assert _sanitize_optional_identity_value("") is None
def test_whitespace(self):
from src.api.routes.git._helpers import _sanitize_optional_identity_value
assert _sanitize_optional_identity_value(" ") is None
def test_valid(self):
from src.api.routes.git._helpers import _sanitize_optional_identity_value
assert _sanitize_optional_identity_value(" alice ") == "alice"
# ── _resolve_current_user_git_identity ──
class TestResolveCurrentUserGitIdentity:
def test_db_is_none(self):
from src.api.routes.git._helpers import _resolve_current_user_git_identity
assert _resolve_current_user_git_identity(None, MagicMock()) is None
def test_no_user_id(self):
from src.api.routes.git._helpers import _resolve_current_user_git_identity
user = MagicMock()
user.id = None
assert _resolve_current_user_git_identity(MagicMock(), user) is None
def test_preference_found(self):
mock_db = MagicMock()
pref = MagicMock()
pref.git_username = "alice"
pref.git_email = "alice@example.com"
mock_db.query.return_value.filter.return_value.first.return_value = pref
user = MagicMock()
user.id = "user-1"
from src.api.routes.git._helpers import _resolve_current_user_git_identity
result = _resolve_current_user_git_identity(mock_db, user)
assert result == ("alice", "alice@example.com")
def test_preference_not_found(self):
mock_db = MagicMock()
mock_db.query.return_value.filter.return_value.first.return_value = None
user = MagicMock()
user.id = "user-1"
from src.api.routes.git._helpers import _resolve_current_user_git_identity
assert _resolve_current_user_git_identity(mock_db, user) is None
def test_preference_missing_git_fields(self):
mock_db = MagicMock()
pref = MagicMock()
pref.git_username = None
pref.git_email = None
mock_db.query.return_value.filter.return_value.first.return_value = pref
user = MagicMock()
user.id = "user-1"
from src.api.routes.git._helpers import _resolve_current_user_git_identity
assert _resolve_current_user_git_identity(mock_db, user) is None
def test_query_exception(self):
mock_db = MagicMock()
mock_db.query.return_value.filter.return_value.first.side_effect = Exception("DB error")
user = MagicMock()
user.id = "user-1"
from src.api.routes.git._helpers import _resolve_current_user_git_identity
assert _resolve_current_user_git_identity(mock_db, user) is None
def test_db_without_query_attr(self):
from src.api.routes.git._helpers import _resolve_current_user_git_identity
assert _resolve_current_user_git_identity(object(), MagicMock()) is None
# ── _apply_git_identity_from_profile ──
class TestApplyGitIdentityFromProfile:
@pytest.mark.asyncio
async def test_identity_resolved_and_applied(self):
mock_service = MagicMock()
mock_service.configure_identity = AsyncMock()
with patch("src.api.routes.git._helpers.get_git_service", return_value=mock_service), \
patch("src.api.routes.git._helpers._resolve_current_user_git_identity",
return_value=("alice", "alice@example.com")):
from src.api.routes.git._helpers import _apply_git_identity_from_profile
await _apply_git_identity_from_profile(42, MagicMock(), MagicMock())
mock_service.configure_identity.assert_awaited_once_with(42, "alice", "alice@example.com")
@pytest.mark.asyncio
async def test_identity_not_resolved(self):
with patch("src.api.routes.git._helpers._resolve_current_user_git_identity",
return_value=None):
from src.api.routes.git._helpers import _apply_git_identity_from_profile
# Should not raise
await _apply_git_identity_from_profile(42, MagicMock(), MagicMock())
@pytest.mark.asyncio
async def test_service_has_no_configure_identity(self):
mock_service = MagicMock(spec=[]) # no configure_identity attribute
with patch("src.api.routes.git._helpers.get_git_service", return_value=mock_service), \
patch("src.api.routes.git._helpers._resolve_current_user_git_identity",
return_value=("alice", "alice@example.com")):
from src.api.routes.git._helpers import _apply_git_identity_from_profile
# Should not raise
await _apply_git_identity_from_profile(42, MagicMock(), MagicMock())
# #endregion Test.Api.GitHelpers
# #region Test.Api.GitHelpers.FinalBranches [C:3] [TYPE Module]
# @defgroup find-by-slug failure, repo-key slug resolution, sanitize, token decrypt, identity await.
from src.api.routes.git._helpers import (
_apply_git_identity_from_profile,
_find_dashboard_id_by_slug,
_resolve_current_user_git_token,
_sanitize_optional_identity_value,
_resolve_repo_key_from_ref,
)
class TestFindDashboardIdBySlugFailures:
@pytest.mark.asyncio
async def test_all_queries_fail_returns_none(self):
client = AsyncMock()
client.get_dashboards_page.side_effect = RuntimeError("superset down")
result = await _find_dashboard_id_by_slug(client, "my-slug")
assert result is None
@pytest.mark.asyncio
async def test_first_variant_fails_second_succeeds(self):
client = AsyncMock()
client.get_dashboards_page.side_effect = [
RuntimeError("bad op"),
(1, [{"id": 42}]),
]
result = await _find_dashboard_id_by_slug(client, "my-slug")
assert result == 42
# #endregion Test.Api.GitHelpers.FinalBranches.FindSlug
class TestResolveRepoKeyFromRefSlug:
@pytest.mark.asyncio
async def test_env_with_slug_returns_slug(self):
config_manager = MagicMock()
env = SimpleNamespace(id="env-1")
config_manager.get_environments.return_value = [env]
with patch("src.api.routes.git._helpers.SupersetClient") as client_cls:
client = AsyncMock()
client_cls.return_value = client
client.get_dashboard = AsyncMock(return_value={"result": {"slug": "my-slug"}})
result = await _resolve_repo_key_from_ref("", 5, config_manager, env_id="env-1")
assert result == "my-slug"
@pytest.mark.asyncio
async def test_env_lookup_failure_falls_back(self):
config_manager = MagicMock()
config_manager.get_environments.side_effect = RuntimeError("cfg down")
result = await _resolve_repo_key_from_ref("", 5, config_manager, env_id="env-1")
assert result == "dashboard-5"
# #endregion Test.Api.GitHelpers.FinalBranches.RepoKey
class TestSanitizeOptionalIdentityValue:
def test_non_string_int_returns_none(self):
assert _sanitize_optional_identity_value(3.14) is None
assert _sanitize_optional_identity_value(["x"]) is None
def test_blank_returns_none(self):
assert _sanitize_optional_identity_value(" ") is None
assert _sanitize_optional_identity_value("") is None
# #endregion Test.Api.GitHelpers.FinalBranches.Sanitize
class TestResolveCurrentUserGitToken:
def test_db_none_returns_none(self):
assert _resolve_current_user_git_token(None, SimpleNamespace(id="u1")) is None
def test_decrypt_failure_returns_none(self):
db = MagicMock()
pref = MagicMock()
pref.git_personal_access_token_encrypted = "encrypted"
db.query.return_value.filter.return_value.first.return_value = pref
with patch("src.api.routes.git._helpers.EncryptionManager") as enc_cls:
enc_cls.return_value.decrypt.side_effect = RuntimeError("bad key")
result = _resolve_current_user_git_token(db, SimpleNamespace(id="u1"))
assert result is None
def test_decrypt_success(self):
db = MagicMock()
pref = MagicMock()
pref.git_personal_access_token_encrypted = "encrypted"
db.query.return_value.filter.return_value.first.return_value = pref
with patch("src.api.routes.git._helpers.EncryptionManager") as enc_cls:
enc_cls.return_value.decrypt.return_value = "pat-1"
result = _resolve_current_user_git_token(db, SimpleNamespace(id="u1"))
assert result == "pat-1"
# #endregion Test.Api.GitHelpers.FinalBranches.Token
class TestApplyGitIdentityAwait:
@pytest.mark.asyncio
async def test_awaitable_identity_result(self):
configure = AsyncMock()
service = MagicMock()
service.configure_identity = configure
with patch("src.api.routes.git._helpers.get_git_service", return_value=service), \
patch("src.api.routes.git._helpers._resolve_current_user_git_identity", return_value=("name", "email")):
await _apply_git_identity_from_profile(5, MagicMock(), SimpleNamespace(id="u1"))
configure.assert_awaited_once_with(5, "name", "email")
# #endregion Test.Api.GitHelpers.FinalBranches.Identity