Files
ss-tools/backend/tests/services/git/test_git_undo_commit.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

149 lines
6.1 KiB
Python

# #region Test.Git.UndoLastCommit [C:3] [TYPE Module] [SEMANTICS test,git,undo,soft-reset,commit]
# @BRIEF Tests for GitServiceBranchMixin.undo_last_commit — BI-safe soft-undo of unpublished commits.
# @RELATION BINDS_TO -> [Services.Branch.UndoLastCommit]
# @TEST_EDGE: no_commits -> HTTPException 409 "No commits to undo"
# @TEST_EDGE: already_pushed -> HTTPException 409 (ahead_count == 0)
# @TEST_EDGE: unpushed_with_upstream -> reset --soft HEAD~1 executed, returns undone hash/message
# @TEST_EDGE: no_upstream -> undo allowed (nothing was ever pushed)
# @TEST_EDGE: detached_head -> HTTPException 409 (active_branch raises TypeError)
import contextlib
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).parent.parent.parent.parent / "src"))
import pytest
from unittest.mock import MagicMock
from fastapi import HTTPException
from src.services.git._branch import GitServiceBranchMixin
class TestableGitUndo(GitServiceBranchMixin):
"""Concrete test class providing _locked and get_repo stubs."""
def __init__(self, mock_repo=None):
self._mock_repo = mock_repo or MagicMock()
async def get_repo(self, dashboard_id):
return self._mock_repo
def _locked(self, dashboard_id):
@contextlib.contextmanager
def _lock():
yield
return _lock()
def _make_repo(*, has_head=True, tracking=None, ahead=1, branch_name="dev"):
repo = MagicMock()
if has_head:
head_commit = MagicMock()
head_commit.hexsha = "abc123def456"
head_commit.message = "feat: test version\n"
repo.head.commit = head_commit
else:
type(repo.head).commit = property(lambda self: (_ for _ in ()).throw(ValueError("no commits")))
active_branch = MagicMock()
active_branch.name = branch_name
active_branch.tracking_branch.return_value = tracking # None or MagicMock(name='origin/dev')
if tracking == "error":
# Detached HEAD: accessing repo.active_branch itself raises TypeError
type(repo).active_branch = property(lambda self: (_ for _ in ()).throw(TypeError("detached HEAD")))
else:
repo.active_branch = active_branch
repo.iter_commits.return_value = iter([MagicMock() for _ in range(ahead)])
return repo
class TestUndoLastCommit:
# #region Test.Git.UndoNoCommits [C:2] [TYPE Function]
@pytest.mark.asyncio
async def test_undo_no_commits_raises_409(self):
"""Empty repo → 409 'No commits to undo'."""
repo = _make_repo(has_head=False)
svc = TestableGitUndo(repo)
with pytest.raises(HTTPException) as exc_info:
await svc.undo_last_commit(1)
assert exc_info.value.status_code == 409
assert "No commits" in exc_info.value.detail
repo.git.reset.assert_not_called()
# #endregion Test.Git.UndoNoCommits
# #region Test.Git.UndoAlreadyPushed [C:2] [TYPE Function]
@pytest.mark.asyncio
async def test_undo_already_pushed_raises_409(self):
"""Last commit already on remote (ahead == 0) → 409, no reset."""
tracking = MagicMock()
tracking.name = "origin/dev"
repo = _make_repo(tracking=tracking, ahead=0)
svc = TestableGitUndo(repo)
with pytest.raises(HTTPException) as exc_info:
await svc.undo_last_commit(1)
assert exc_info.value.status_code == 409
assert "already published" in exc_info.value.detail
repo.git.reset.assert_not_called()
# #endregion Test.Git.UndoAlreadyPushed
# #region Test.Git.UndoUnpushedSuccess [C:2] [TYPE Function]
@pytest.mark.asyncio
async def test_undo_unpushed_commit_succeeds(self):
"""Unpushed commit (ahead == 1) → soft reset executed, undone metadata returned."""
tracking = MagicMock()
tracking.name = "origin/dev"
repo = _make_repo(tracking=tracking, ahead=1)
svc = TestableGitUndo(repo)
result = await svc.undo_last_commit(1)
repo.git.reset.assert_called_once_with("--soft", "HEAD~1")
assert result["status"] == "success"
assert result["undone_hash"] == "abc123def456"
assert result["undone_message"] == "feat: test version"
# #endregion Test.Git.UndoUnpushedSuccess
# #region Test.Git.UndoNoUpstream [C:2] [TYPE Function]
@pytest.mark.asyncio
async def test_undo_no_upstream_allowed(self):
"""No upstream tracking branch → nothing pushed, undo allowed."""
repo = _make_repo(tracking=None)
svc = TestableGitUndo(repo)
result = await svc.undo_last_commit(1)
repo.git.reset.assert_called_once_with("--soft", "HEAD~1")
assert result["status"] == "success"
# #endregion Test.Git.UndoNoUpstream
# #region Test.Git.UndoDetachedHead [C:2] [TYPE Function]
@pytest.mark.asyncio
async def test_undo_detached_head_raises_409(self):
"""Detached HEAD (active_branch raises TypeError) → 409."""
repo = _make_repo(has_head=True, tracking="error")
svc = TestableGitUndo(repo)
with pytest.raises(HTTPException) as exc_info:
await svc.undo_last_commit(1)
assert exc_info.value.status_code == 409
repo.git.reset.assert_not_called()
# #endregion Test.Git.UndoDetachedHead
# #region Test.Git.UndoTrackingBranchError [C:2] [TYPE Function]
@pytest.mark.asyncio
async def test_undo_tracking_branch_exception_allowed(self):
"""tracking_branch() raises → treated as no upstream, undo allowed."""
repo = MagicMock()
head_commit = MagicMock()
head_commit.hexsha = "abc123def456"
head_commit.message = "feat: test version\n"
repo.head.commit = head_commit
active_branch = MagicMock()
active_branch.name = "dev"
active_branch.tracking_branch.side_effect = Exception("cannot resolve upstream")
repo.active_branch = active_branch
svc = TestableGitUndo(repo)
result = await svc.undo_last_commit(1)
repo.git.reset.assert_called_once_with("--soft", "HEAD~1")
assert result["status"] == "success"
assert result["undone_hash"] == "abc123def456"
# #endregion Test.Git.UndoTrackingBranchError
# #endregion Test.Git.UndoLastCommit