test: 7 agents — plugins ~158 tests, extractor 98-100%, dashboard routes 99-100%, git services 94-100%, services 94-100%, dataset_review 100%. Fix test_api_key_routes.py sys.modules pollution
This commit is contained in:
174
backend/tests/services/git/test_git_base_coverage.py
Normal file
174
backend/tests/services/git/test_git_base_coverage.py
Normal file
@@ -0,0 +1,174 @@
|
||||
# #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
|
||||
Reference in New Issue
Block a user