"""Unit tests for dashboard.handlers.git — _run_git helper + endpoint shapes.""" from __future__ import annotations import subprocess import sys from pathlib import Path from unittest.mock import patch import pytest PROJECT_ROOT = Path(__file__).resolve().parents[1] DASH = PROJECT_ROOT / "dashboard" if str(DASH) not in sys.path: sys.path.insert(0, str(DASH)) @pytest.fixture(scope="module") def git_module(): from handlers import git as _g # type: ignore return _g @pytest.fixture def handler(git_module): """A bare instance of GitHandlers with a captured send_json.""" class _Stub(git_module.GitHandlers): def __init__(self): self.captured = None self.captured_code = None def send_json(self, data, code=200): self.captured = data self.captured_code = code return _Stub() def test_run_git_is_a_subprocess_call(handler, git_module, tmp_path): """_run_git must use subprocess.run with the supplied cwd + timeout.""" with patch.object(git_module.subprocess, "run") as mock_run: mock_run.return_value = subprocess.CompletedProcess( args=["git", "status"], returncode=0, stdout="clean\n", stderr="" ) result = handler._run_git(tmp_path, ["status"], timeout=3) mock_run.assert_called_once() args, kwargs = mock_run.call_args assert args[0] == ["git", "status"] assert kwargs["cwd"] == str(tmp_path) assert kwargs["timeout"] == 3 assert kwargs["capture_output"] is True assert kwargs["text"] is True assert result.stdout == "clean\n" def test_run_git_default_timeout_is_5(handler, git_module, tmp_path): with patch.object(git_module.subprocess, "run") as mock_run: mock_run.return_value = subprocess.CompletedProcess( args=["git", "log"], returncode=0, stdout="", stderr="" ) handler._run_git(tmp_path, ["log"]) _, kwargs = mock_run.call_args assert kwargs["timeout"] == 5 def test_legacy_git_commit_handler_is_gone(git_module): """/api/git-commit was consolidated into /api/eco/git-commit.""" assert not hasattr(git_module.GitHandlers, "handle_git_commit") def test_git_status_uses_echo_core_workspace(handler, git_module): """handle_git_status must target constants.GIT_WORKSPACE (echo-core), not clawd.""" captured_workspaces = [] def fake_run(_self, workspace, args, timeout=5): captured_workspaces.append(workspace) stdout_map = { ("branch", "--show-current"): "master\n", ("log", "-1", "--format=%h|%s|%cr"): "abc1234|test commit|1 hour ago\n", ("status", "--short"): "", ("diff", "--stat", "--cached"): "", ("diff", "--stat"): "", } return subprocess.CompletedProcess( args=["git", *args], returncode=0, stdout=stdout_map.get(tuple(args), ""), stderr="", ) with patch.object(git_module.GitHandlers, "_run_git", fake_run): handler.handle_git_status() import constants # type: ignore assert all(w == constants.GIT_WORKSPACE for w in captured_workspaces) assert handler.captured is not None assert handler.captured["branch"] == "master" def test_git_status_parses_uncommitted_paths(handler, git_module): def fake_run(_self, workspace, args, timeout=5): if args == ["status", "--short"]: return subprocess.CompletedProcess( args=["git"] + args, returncode=0, stdout=" M src/foo.py\n?? new_file.txt\n", stderr="", ) if args[:1] == ["branch"]: return subprocess.CompletedProcess( args=["git"] + args, returncode=0, stdout="feat-x\n", stderr="", ) if args[:1] == ["log"]: return subprocess.CompletedProcess( args=["git"] + args, returncode=0, stdout="deadbee|msg|2 minutes ago\n", stderr="", ) return subprocess.CompletedProcess( args=["git"] + args, returncode=0, stdout="", stderr="", ) with patch.object(git_module.GitHandlers, "_run_git", fake_run): handler.handle_git_status() data = handler.captured assert data is not None assert data["uncommittedCount"] == 2 assert data["clean"] is False paths = [u["path"] for u in data["uncommittedParsed"]] statuses = {u["status"] for u in data["uncommittedParsed"]} assert "src/foo.py" in paths assert "new_file.txt" in paths assert "M" in statuses assert "??" in statuses