# #region Test.Git.Base.Coverage [C:3] [TYPE Module] [SEMANTICS test, git, base, coverage, repo, path, resolve] # @BRIEF Additional edge coverage for GitServiceBase — _resolve_base_path relative root, _update_repo_local_path exception, _get_repo_path db-path-exists conditions, init_repo stale unlink exception. # @RELATION BINDS_TO -> [GitServiceBase] import contextlib import sys from pathlib import Path sys.path.insert(0, str(Path(__file__).parent.parent.parent.parent / "src")) from unittest.mock import AsyncMock, MagicMock, patch, PropertyMock import pytest from git.exc import InvalidGitRepositoryError from git import Repo from src.services.git._base import GitServiceBase class TestResolveBasePathCoverage: """Cover _resolve_base_path relative-root and combined-path branches.""" def test_relative_root_resolved_against_project_root(self): """Root path is relative → resolved against project root.""" with patch.object(GitServiceBase, '_ensure_base_path_exists'), \ patch("src.services.git._base.SessionLocal") as mock_sl: mock_db = MagicMock() mock_sl.return_value = mock_db config = MagicMock() config.payload = {"settings": {"storage": {"root_path": "data", "repo_path": "repositories"}}} mock_db.query.return_value.filter.return_value.first.return_value = config svc = GitServiceBase(base_path="git_repos") # Verify the _resolve_base_path already ran in __init__ # We can test this by checking that base_path has been resolved assert svc.base_path is not None def test_db_config_with_repo_path(self): """repo_path is relative → combined with root.""" with patch.object(GitServiceBase, '_ensure_base_path_exists'), \ patch("src.services.git._base.SessionLocal") as mock_sl: mock_db = MagicMock() mock_sl.return_value = mock_db config = MagicMock() config.payload = {"settings": {"storage": {"root_path": "/data", "repo_path": "my-repos"}}} mock_db.query.return_value.filter.return_value.first.return_value = config svc = GitServiceBase(base_path="git_repos") assert svc.base_path is not None # When root_path is absolute and repo_path relative → root / repo_path assert "/data/my-repos" in svc.base_path def test_db_config_absolute_repo_path(self): """repo_path is absolute → returned directly.""" with patch.object(GitServiceBase, '_ensure_base_path_exists'), \ patch("src.services.git._base.SessionLocal") as mock_sl: mock_db = MagicMock() mock_sl.return_value = mock_db config = MagicMock() config.payload = {"settings": {"storage": {"root_path": "/data", "repo_path": "/custom/path"}}} mock_db.query.return_value.filter.return_value.first.return_value = config svc = GitServiceBase(base_path="git_repos") assert "/custom/path" in svc.base_path class TestUpdateRepoLocalPathCoverage: """Cover exception handler in _update_repo_local_path.""" def test_session_query_exception_caught(self): """SessionLocal creation fails → exception caught, no raise.""" with patch.object(GitServiceBase, '_ensure_base_path_exists'), \ patch.object(GitServiceBase, '_resolve_base_path', return_value="/tmp/test"): svc = GitServiceBase(base_path="/tmp/test") with patch("src.services.git._base.SessionLocal", side_effect=Exception("session error")): # Should not raise svc._update_repo_local_path(1, "/tmp/repo") def test_session_commit_exception_caught(self): """commit() fails → exception caught, no raise.""" with patch.object(GitServiceBase, '_ensure_base_path_exists'), \ patch.object(GitServiceBase, '_resolve_base_path', return_value="/tmp/test"): svc = GitServiceBase(base_path="/tmp/test") mock_session = MagicMock() mock_db_repo = MagicMock() mock_session.query.return_value.filter.return_value.first.return_value = mock_db_repo mock_session.commit.side_effect = Exception("commit error") with patch("src.services.git._base.SessionLocal", return_value=mock_session): svc._update_repo_local_path(1, "/tmp/repo") class TestGetRepoPathCoverage: """Cover DB-path-exists-conditions in _get_repo_path.""" @pytest.mark.asyncio async def test_db_path_exists_not_legacy(self): """DB local_path exists, not legacy → returned directly.""" with patch.object(GitServiceBase, '_ensure_base_path_exists'): svc = GitServiceBase(base_path="git_repos") svc.base_path = "/tmp/new-base" svc.legacy_base_path = "/tmp/legacy" svc._uses_default_base_path = True with patch("src.services.git._base.SessionLocal") as mock_sl: mock_db = MagicMock() mock_sl.return_value = mock_db mock_db_repo = MagicMock() mock_db_repo.local_path = "/tmp/custom-path/repo" mock_db.query.return_value.filter.return_value.first.return_value = mock_db_repo with patch("os.path.exists", return_value=True), \ patch("os.path.abspath", side_effect=lambda x: x): result = await svc._get_repo_path(1, "key") assert result == "/tmp/custom-path/repo" @pytest.mark.asyncio async def test_db_path_exists_not_legacy_no_migration(self): """DB local_path exists, base_path differs from legacy but path outside legacy → returned directly.""" with patch.object(GitServiceBase, '_ensure_base_path_exists'): svc = GitServiceBase(base_path="git_repos") svc.base_path = "/tmp/new-base" svc.legacy_base_path = "/tmp/legacy" svc._uses_default_base_path = True with patch("src.services.git._base.SessionLocal") as mock_sl: mock_db = MagicMock() mock_sl.return_value = mock_db mock_db_repo = MagicMock() mock_db_repo.local_path = "/tmp/independent/path" mock_db.query.return_value.filter.return_value.first.return_value = mock_db_repo with patch("os.path.exists", return_value=True), \ patch("os.path.abspath", side_effect=lambda x: x): result = await svc._get_repo_path(1, "key") assert result == "/tmp/independent/path" @pytest.mark.asyncio async def test_db_query_exception_caught(self): """DB query raises → warning logged, fallback path used.""" with patch.object(GitServiceBase, '_ensure_base_path_exists'): svc = GitServiceBase(base_path="git_repos") svc.base_path = "/tmp/base" svc.legacy_base_path = "/tmp/legacy" svc._uses_default_base_path = True with patch("src.services.git._base.SessionLocal", side_effect=Exception("query failed")): result = await svc._get_repo_path(1, "my-key") assert "/tmp/base/my-key" in result class TestInitRepoCoverage: """Cover stale_path.unlink exception in init_repo.""" @pytest.mark.asyncio async def test_stale_unlink_exception(self): """shutil.rmtree succeeds, stale_path.unlink raises → pass, clone proceeds.""" with patch.object(GitServiceBase, '_ensure_base_path_exists'), \ patch.object(GitServiceBase, '_resolve_base_path', return_value="/tmp/test"): svc = GitServiceBase(base_path="/tmp/test") with patch.object(svc, "_get_repo_path", new_callable=AsyncMock, return_value="/tmp/repo"), \ patch("os.path.exists", return_value=True), \ patch("src.services.git._base.run_blocking") as mock_blocking, \ patch.object(svc, "_clone_with_auth", new_callable=AsyncMock) as mock_clone, \ patch.object(svc, "_ensure_gitflow_branches", create=True), \ patch.object(svc, '_locked', return_value=contextlib.nullcontext()), \ patch("pathlib.Path.exists", return_value=True): # mkdir OK, Repo raises InvalidGitRepositoryError, rmtree succeeds, unlink raises mock_blocking.side_effect = [ None, # mkdir InvalidGitRepositoryError("invalid"), # Repo raises None, # shutil.rmtree succeeds (ignore_errors=True) Exception("unlink fail"), # stale_path.unlink raises → caught MagicMock(), # Repo from clone ] mock_clone.return_value = MagicMock() result = await svc.init_repo(1, "https://remote.com/repo.git", "pat") assert result is not None # #endregion Test.Git.Base.Coverage