203 lines
6.0 KiB
Python
203 lines
6.0 KiB
Python
"""Tests for the Git Hand.
|
|
|
|
Covers:
|
|
- Destructive operation gating
|
|
- Successful git operations (status, log)
|
|
- Convenience wrappers
|
|
- GitResult dataclass defaults
|
|
- Info summary
|
|
"""
|
|
|
|
import pytest
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Destructive operation gating
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
def test_is_destructive_detects_force_push():
|
|
"""Force-push should be flagged as destructive."""
|
|
from infrastructure.hands.git import GitHand
|
|
|
|
hand = GitHand()
|
|
assert hand._is_destructive("push --force origin main") is True
|
|
assert hand._is_destructive("push -f origin main") is True
|
|
|
|
|
|
def test_is_destructive_detects_hard_reset():
|
|
"""Hard reset should be flagged as destructive."""
|
|
from infrastructure.hands.git import GitHand
|
|
|
|
hand = GitHand()
|
|
assert hand._is_destructive("reset --hard HEAD~1") is True
|
|
|
|
|
|
def test_is_destructive_safe_ops():
|
|
"""Safe operations should not be flagged."""
|
|
from infrastructure.hands.git import GitHand
|
|
|
|
hand = GitHand()
|
|
assert hand._is_destructive("status") is False
|
|
assert hand._is_destructive("log --oneline -5") is False
|
|
assert hand._is_destructive("push origin main") is False
|
|
assert hand._is_destructive("commit -m 'test'") is False
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_run_blocks_destructive_by_default():
|
|
"""Destructive ops should be blocked unless allow_destructive=True."""
|
|
from infrastructure.hands.git import GitHand
|
|
|
|
hand = GitHand()
|
|
result = await hand.run("push --force origin main")
|
|
assert result.success is False
|
|
assert result.requires_confirmation is True
|
|
assert "blocked" in result.error.lower()
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_run_allows_destructive_with_flag():
|
|
"""Destructive ops should execute when allow_destructive=True."""
|
|
from infrastructure.hands.git import GitHand
|
|
|
|
# This will fail because there's no remote, but it should NOT be blocked
|
|
hand = GitHand()
|
|
result = await hand.run("push --force origin nonexistent-branch", allow_destructive=True)
|
|
# It will fail (no remote), but it was NOT blocked by the gate
|
|
assert result.requires_confirmation is False
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Successful operations
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_run_git_status():
|
|
"""git status should succeed in a git repo."""
|
|
from infrastructure.hands.git import GitHand
|
|
|
|
hand = GitHand()
|
|
result = await hand.run("status --short")
|
|
assert result.success is True
|
|
assert result.latency_ms > 0
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_run_git_log():
|
|
"""git log should succeed in a git repo."""
|
|
from infrastructure.hands.git import GitHand
|
|
|
|
hand = GitHand()
|
|
result = await hand.run("log --oneline -3")
|
|
assert result.success is True
|
|
assert result.output # should have at least one commit
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Convenience wrappers
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_status_wrapper():
|
|
"""status() convenience wrapper should work."""
|
|
from infrastructure.hands.git import GitHand
|
|
|
|
hand = GitHand()
|
|
result = await hand.status()
|
|
assert result.success is True
|
|
assert result.operation == "git status --short"
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_log_wrapper():
|
|
"""log() convenience wrapper should work."""
|
|
from infrastructure.hands.git import GitHand
|
|
|
|
hand = GitHand()
|
|
result = await hand.log(count=3)
|
|
assert result.success is True
|
|
assert "git log" in result.operation
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_diff_wrapper():
|
|
"""diff() convenience wrapper should work."""
|
|
from infrastructure.hands.git import GitHand
|
|
|
|
hand = GitHand()
|
|
result = await hand.diff()
|
|
assert result.success is True
|
|
assert result.operation == "git diff"
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_diff_staged_wrapper():
|
|
"""diff(staged=True) should pass --cached."""
|
|
from infrastructure.hands.git import GitHand
|
|
|
|
hand = GitHand()
|
|
result = await hand.diff(staged=True)
|
|
assert result.success is True
|
|
assert result.operation == "git diff --cached"
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# GitResult dataclass
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
def test_git_result_defaults():
|
|
"""GitResult should have sensible defaults."""
|
|
from infrastructure.hands.git import GitResult
|
|
|
|
r = GitResult(operation="git status", success=True)
|
|
assert r.output == ""
|
|
assert r.error == ""
|
|
assert r.latency_ms == 0.0
|
|
assert r.requires_confirmation is False
|
|
assert r.metadata == {}
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Info summary
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
def test_info_returns_summary():
|
|
"""info() should return a dict with repo_dir and timeout."""
|
|
from infrastructure.hands.git import GitHand
|
|
|
|
hand = GitHand()
|
|
i = hand.info()
|
|
assert "repo_dir" in i
|
|
assert "default_timeout" in i
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Tool registration
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
def test_persona_hand_map():
|
|
"""Forge and Helm should have both shell and git access."""
|
|
from infrastructure.hands.tools import get_local_hands_for_persona
|
|
|
|
forge_hands = get_local_hands_for_persona("forge")
|
|
assert "hand_shell" in forge_hands
|
|
assert "hand_git" in forge_hands
|
|
|
|
helm_hands = get_local_hands_for_persona("helm")
|
|
assert "hand_shell" in helm_hands
|
|
assert "hand_git" in helm_hands
|
|
|
|
# Echo only gets git
|
|
echo_hands = get_local_hands_for_persona("echo")
|
|
assert "hand_git" in echo_hands
|
|
assert "hand_shell" not in echo_hands
|
|
|
|
# Quill gets neither
|
|
quill_hands = get_local_hands_for_persona("quill")
|
|
assert quill_hands == []
|