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:
@@ -265,4 +265,237 @@ class TestClosedService:
|
||||
with pytest.raises(RuntimeError, match="closed"):
|
||||
with svc._locked(1):
|
||||
pass
|
||||
|
||||
|
||||
class TestResolveBasePathEdge:
|
||||
"""_resolve_base_path — DB config path with non-absolute root and absolute repo_path."""
|
||||
|
||||
def test_non_absolute_root_with_absolute_repo_path(self):
|
||||
"""root_path relative, repo_path absolute → uses resolved root + absolute repo_path."""
|
||||
with patch.object(GitServiceBase, '_ensure_base_path_exists'), \
|
||||
patch("src.services.git._base.SessionLocal") as mock_sl_cls:
|
||||
mock_session = MagicMock()
|
||||
mock_sl_cls.return_value = mock_session
|
||||
config_row = MagicMock()
|
||||
config_row.payload = {
|
||||
"settings": {
|
||||
"storage": {
|
||||
"root_path": "relative/storage",
|
||||
"repo_path": "/absolute/repo/path",
|
||||
}
|
||||
}
|
||||
}
|
||||
mock_session.query.return_value.filter.return_value.first.return_value = config_row
|
||||
|
||||
svc = GitServiceBase(base_path="git_repos")
|
||||
assert svc.base_path is not None
|
||||
assert "/absolute/repo/path" in svc.base_path
|
||||
|
||||
def test_absolute_root_with_relative_repo(self):
|
||||
"""root_path absolute, repo_path relative → combined path."""
|
||||
with patch.object(GitServiceBase, '_ensure_base_path_exists'), \
|
||||
patch("src.services.git._base.SessionLocal") as mock_sl_cls:
|
||||
mock_session = MagicMock()
|
||||
mock_sl_cls.return_value = mock_session
|
||||
config_row = MagicMock()
|
||||
config_row.payload = {
|
||||
"settings": {
|
||||
"storage": {
|
||||
"root_path": "/absolute/storage",
|
||||
"repo_path": "repositories",
|
||||
}
|
||||
}
|
||||
}
|
||||
mock_session.query.return_value.filter.return_value.first.return_value = config_row
|
||||
|
||||
svc = GitServiceBase(base_path="git_repos")
|
||||
assert svc.base_path is not None
|
||||
assert "repositories" in svc.base_path
|
||||
|
||||
def test_config_with_empty_payload_storage(self):
|
||||
"""Payload exists but storage settings empty → fallback."""
|
||||
with patch.object(GitServiceBase, '_ensure_base_path_exists'), \
|
||||
patch("src.services.git._base.SessionLocal") as mock_sl_cls:
|
||||
mock_session = MagicMock()
|
||||
mock_sl_cls.return_value = mock_session
|
||||
config_row = MagicMock()
|
||||
config_row.payload = {"settings": {"storage": {}}}
|
||||
mock_session.query.return_value.filter.return_value.first.return_value = config_row
|
||||
|
||||
svc = GitServiceBase(base_path="git_repos")
|
||||
assert svc.base_path is not None
|
||||
|
||||
def test_no_config_row(self):
|
||||
"""No config row in DB → fallback path."""
|
||||
with patch.object(GitServiceBase, '_ensure_base_path_exists'), \
|
||||
patch("src.services.git._base.SessionLocal") as mock_sl_cls:
|
||||
mock_session = MagicMock()
|
||||
mock_sl_cls.return_value = mock_session
|
||||
mock_session.query.return_value.filter.return_value.first.return_value = None
|
||||
svc = GitServiceBase(base_path="git_repos")
|
||||
assert svc.base_path is not None
|
||||
|
||||
def test_custom_base_path_uses_fallback(self):
|
||||
"""Custom base_path (not 'git_repos') → returns fallback_path directly, no DB query."""
|
||||
# Don't patch _resolve_base_path — test it directly
|
||||
# Patch _ensure_base_path_exists so it doesn't create directories
|
||||
actual_msg = "git_repos" # We need to verify no DB call, so we pass a custom path
|
||||
with patch.object(GitServiceBase, '_ensure_base_path_exists'), \
|
||||
patch("src.services.git._base.SessionLocal") as mock_sl:
|
||||
# For custom base path, _resolve_base_path returns fallback without DB query
|
||||
svc = GitServiceBase(base_path="/custom/path")
|
||||
# Should use fallback, not DB
|
||||
assert "custom" in svc.base_path
|
||||
mock_sl.assert_not_called() # No DB query for custom paths
|
||||
|
||||
|
||||
class TestGetRepoPathEdge:
|
||||
"""_get_repo_path — DB migration path and edge cases."""
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_db_path_with_migration_condition(self):
|
||||
"""DB path starts with legacy base and base_path != legacy → migrate."""
|
||||
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_cls:
|
||||
mock_db = MagicMock()
|
||||
mock_sl_cls.return_value = mock_db
|
||||
mock_db_repo = MagicMock()
|
||||
mock_db_repo.local_path = "/tmp/legacy/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), \
|
||||
patch.object(svc, '_migrate_repo_directory', new_callable=AsyncMock, return_value="/tmp/new-base/dashboard"):
|
||||
result = await svc._get_repo_path(1)
|
||||
assert result == "/tmp/new-base/dashboard"
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_db_path_not_starting_with_legacy(self):
|
||||
"""DB path exists but does not start with legacy_base → 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_cls:
|
||||
mock_db = MagicMock()
|
||||
mock_sl_cls.return_value = mock_db
|
||||
mock_db_repo = MagicMock()
|
||||
mock_db_repo.local_path = "/tmp/custom/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, "my-key")
|
||||
assert result == "/tmp/custom/repo"
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_legacy_path_does_not_exist_no_migration(self):
|
||||
"""Legacy ID path does not exist → no migration, target path returned."""
|
||||
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_cls:
|
||||
mock_db = MagicMock()
|
||||
mock_sl_cls.return_value = mock_db
|
||||
mock_db.query.return_value.filter.return_value.first.return_value = None
|
||||
with patch("os.path.exists", side_effect=lambda p: False), \
|
||||
patch.object(svc, '_update_repo_local_path') as mock_update:
|
||||
result = await svc._get_repo_path(1, "my-key")
|
||||
assert result == "/tmp/new-base/my-key"
|
||||
# target_path doesn't exist, legacy_id_path doesn't exist
|
||||
# → returns target_path without update
|
||||
mock_update.assert_not_called()
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_legacy_path_exists_new_base_differs(self):
|
||||
"""Legacy path exists, target doesn't → migrate."""
|
||||
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
|
||||
|
||||
# No DB record
|
||||
with patch("src.services.git._base.SessionLocal") as mock_sl_cls:
|
||||
mock_db = MagicMock()
|
||||
mock_sl_cls.return_value = mock_db
|
||||
mock_db.query.return_value.filter.return_value.first.return_value = None
|
||||
# legacy path exists, target path does not exist
|
||||
legacy_path = "/tmp/legacy/1"
|
||||
target_path = "/tmp/new-base/dashboard"
|
||||
with patch("os.path.exists", side_effect=lambda p: p == legacy_path), \
|
||||
patch.object(svc, '_migrate_repo_directory', new_callable=AsyncMock, return_value=target_path):
|
||||
result = await svc._get_repo_path(1)
|
||||
assert result == target_path
|
||||
|
||||
|
||||
class TestInitRepoEdgePaths:
|
||||
"""init_repo — additional edge paths."""
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_existing_repo_opens_successfully(self):
|
||||
"""Existing repo path → opens directly."""
|
||||
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_repo = MagicMock()
|
||||
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", return_value=mock_repo) as mock_blocking, \
|
||||
patch.object(svc, "_ensure_gitflow_branches", create=True), \
|
||||
patch.object(svc, '_locked', return_value=contextlib.nullcontext()):
|
||||
mock_blocking.side_effect = [None, mock_repo] # mkdir then Repo
|
||||
result = await svc.init_repo(1, "https://remote.com/repo.git", "pat")
|
||||
assert result is not None
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_stale_path_unlink_exception(self):
|
||||
"""stale_path.unlink fails → caught, 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")
|
||||
mock_repo = MagicMock()
|
||||
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, return_value=mock_repo), \
|
||||
patch.object(svc, "_ensure_gitflow_branches", create=True), \
|
||||
patch.object(svc, '_locked', return_value=contextlib.nullcontext()), \
|
||||
patch("pathlib.Path.exists", return_value=True):
|
||||
|
||||
# mkdir → InvalidGitRepositoryError → rmtree → unlink fails → clone
|
||||
mock_blocking.side_effect = [
|
||||
None, # Path(repo_path).parent.mkdir
|
||||
InvalidGitRepositoryError("invalid"), # Repo() call
|
||||
None, # shutil.rmtree
|
||||
Exception("unlink failed"), # stale_path.unlink
|
||||
]
|
||||
result = await svc.init_repo(1, "https://remote.com/repo.git", "pat")
|
||||
assert result is not None
|
||||
|
||||
|
||||
class TestMigrateRepoDirectoryOSError:
|
||||
"""_migrate_repo_directory — OSError → shutil.move fallback."""
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_os_replace_oserror_falls_back(self):
|
||||
"""os.replace raises OSError → shutil.move used."""
|
||||
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("os.path.exists", return_value=False), \
|
||||
patch.object(svc, '_update_repo_local_path'), \
|
||||
patch("src.services.git._base.run_blocking", new_callable=AsyncMock) as mock_blocking:
|
||||
# mkdir succeeds, os.replace fails with OSError, shutil.move succeeds
|
||||
mock_blocking.side_effect = [None, OSError("rename failed"), None]
|
||||
result = await svc._migrate_repo_directory(1, "/source", "/target")
|
||||
assert result == "/target"
|
||||
# #endregion Test.Git.Base.Edge
|
||||
|
||||
Reference in New Issue
Block a user