2026-05-06 12:15:46 -07:00
|
|
|
|
from pathlib import Path
|
|
|
|
|
|
|
|
|
|
|
|
import pytest
|
|
|
|
|
|
|
|
|
|
|
|
from drill.backend import Backend, load_backend
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.fixture
|
|
|
|
|
|
def backends_dir():
|
|
|
|
|
|
return Path(__file__).parent.parent / "backends"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class TestLoadBackend:
|
|
|
|
|
|
def test_loads_claude_backend(self, backends_dir):
|
|
|
|
|
|
backend = load_backend("claude", backends_dir)
|
|
|
|
|
|
assert backend.name == "claude"
|
|
|
|
|
|
assert backend.cli == "claude"
|
|
|
|
|
|
assert "--dangerously-skip-permissions" in backend.args
|
|
|
|
|
|
|
|
|
|
|
|
def test_loads_codex_backend(self, backends_dir):
|
|
|
|
|
|
backend = load_backend("codex", backends_dir)
|
|
|
|
|
|
assert backend.name == "codex"
|
|
|
|
|
|
assert backend.cli == "codex"
|
|
|
|
|
|
|
|
|
|
|
|
def test_unknown_backend_raises(self, backends_dir):
|
|
|
|
|
|
with pytest.raises(FileNotFoundError):
|
|
|
|
|
|
load_backend("nonexistent", backends_dir)
|
|
|
|
|
|
|
|
|
|
|
|
def test_loads_claude_opus_4_6_variant(self, backends_dir, monkeypatch):
|
|
|
|
|
|
monkeypatch.setenv("SUPERPOWERS_ROOT", "/tmp/sp")
|
|
|
|
|
|
backend = load_backend("claude-opus-4-6", backends_dir)
|
|
|
|
|
|
assert backend.name == "claude-opus-4-6"
|
|
|
|
|
|
assert backend.family == "claude"
|
|
|
|
|
|
assert backend.model == "claude-opus-4-6"
|
|
|
|
|
|
|
2026-05-06 15:09:59 -07:00
|
|
|
|
def test_loads_gemini_default_and_flash_variant(self, backends_dir):
|
|
|
|
|
|
backend = load_backend("gemini", backends_dir)
|
|
|
|
|
|
assert backend.name == "gemini"
|
|
|
|
|
|
assert backend.family == "gemini"
|
|
|
|
|
|
assert backend.model == "auto-gemini-3"
|
|
|
|
|
|
|
|
|
|
|
|
flash_backend = load_backend("gemini-2-5-flash", backends_dir)
|
|
|
|
|
|
assert flash_backend.name == "gemini-2-5-flash"
|
|
|
|
|
|
assert flash_backend.family == "gemini"
|
|
|
|
|
|
assert flash_backend.model == "gemini-2.5-flash"
|
|
|
|
|
|
|
2026-05-06 12:15:46 -07:00
|
|
|
|
|
|
|
|
|
|
class TestBackendBuildCommand:
|
|
|
|
|
|
def test_claude_build_command(self, backends_dir, monkeypatch):
|
|
|
|
|
|
monkeypatch.setenv("SUPERPOWERS_ROOT", "/tmp/superpowers")
|
|
|
|
|
|
backend = load_backend("claude", backends_dir)
|
|
|
|
|
|
cmd = backend.build_command("/tmp/workdir")
|
|
|
|
|
|
assert cmd[0] == "claude"
|
|
|
|
|
|
assert "--plugin-dir" in cmd
|
|
|
|
|
|
assert "/tmp/superpowers" in cmd
|
|
|
|
|
|
|
|
|
|
|
|
def test_codex_build_command(self, backends_dir, monkeypatch):
|
|
|
|
|
|
monkeypatch.setenv("SUPERPOWERS_ROOT", "/tmp/superpowers")
|
|
|
|
|
|
backend = load_backend("codex", backends_dir)
|
|
|
|
|
|
cmd = backend.build_command("/tmp/workdir")
|
|
|
|
|
|
assert cmd[0] == "codex"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class TestBackendEnvValidation:
|
|
|
|
|
|
def test_missing_env_raises(self, backends_dir, monkeypatch):
|
|
|
|
|
|
monkeypatch.delenv("ANTHROPIC_API_KEY", raising=False)
|
|
|
|
|
|
monkeypatch.delenv("SUPERPOWERS_ROOT", raising=False)
|
|
|
|
|
|
backend = load_backend("claude", backends_dir)
|
|
|
|
|
|
with pytest.raises(EnvironmentError, match="ANTHROPIC_API_KEY"):
|
|
|
|
|
|
backend.validate_env()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class TestBackendIdleDetection:
|
|
|
|
|
|
def test_ready_pattern_matches(self, backends_dir):
|
|
|
|
|
|
backend = load_backend("claude", backends_dir)
|
|
|
|
|
|
assert backend.is_ready_line("❯ ")
|
|
|
|
|
|
assert backend.is_ready_line("Human: ")
|
|
|
|
|
|
assert not backend.is_ready_line("Running tool...")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class TestBackendModelExtraction:
|
|
|
|
|
|
def test_extract_model_from_args(self, backends_dir, monkeypatch):
|
|
|
|
|
|
monkeypatch.setenv("SUPERPOWERS_ROOT", "/tmp/sp")
|
|
|
|
|
|
backend = load_backend("claude", backends_dir)
|
|
|
|
|
|
assert backend.model == "opus"
|
|
|
|
|
|
|
|
|
|
|
|
def test_no_model_flag_returns_none(self):
|
|
|
|
|
|
backend = Backend(
|
|
|
|
|
|
name="test",
|
|
|
|
|
|
cli="test",
|
|
|
|
|
|
args=["--foo", "bar"],
|
|
|
|
|
|
required_env=[],
|
|
|
|
|
|
hooks={"pre_run": [], "post_run": []},
|
|
|
|
|
|
shutdown="/exit",
|
|
|
|
|
|
idle={},
|
|
|
|
|
|
startup_timeout=30,
|
|
|
|
|
|
terminal={},
|
|
|
|
|
|
session_logs={},
|
|
|
|
|
|
)
|
|
|
|
|
|
assert backend.model is None
|
|
|
|
|
|
|
|
|
|
|
|
def test_extracts_from_short_m_flag(self):
|
|
|
|
|
|
backend = Backend(
|
|
|
|
|
|
name="test",
|
|
|
|
|
|
cli="test",
|
|
|
|
|
|
args=["-m", "gemini-2.5-flash"],
|
|
|
|
|
|
required_env=[],
|
|
|
|
|
|
hooks={"pre_run": [], "post_run": []},
|
|
|
|
|
|
shutdown="/exit",
|
|
|
|
|
|
idle={},
|
|
|
|
|
|
startup_timeout=30,
|
|
|
|
|
|
terminal={},
|
|
|
|
|
|
session_logs={},
|
|
|
|
|
|
)
|
|
|
|
|
|
assert backend.model == "gemini-2.5-flash"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class TestBackendFamily:
|
|
|
|
|
|
def test_claude_backend_family(self, backends_dir, monkeypatch):
|
|
|
|
|
|
monkeypatch.setenv("SUPERPOWERS_ROOT", "/tmp/sp")
|
|
|
|
|
|
backend = load_backend("claude", backends_dir)
|
|
|
|
|
|
assert backend.family == "claude"
|
|
|
|
|
|
|
|
|
|
|
|
def test_codex_backend_family(self, backends_dir):
|
|
|
|
|
|
backend = load_backend("codex", backends_dir)
|
|
|
|
|
|
assert backend.family == "codex"
|
|
|
|
|
|
|
|
|
|
|
|
def test_variant_name_preserves_family(self):
|
|
|
|
|
|
backend = Backend(
|
|
|
|
|
|
name="claude-opus-4-6",
|
|
|
|
|
|
cli="claude",
|
|
|
|
|
|
args=[],
|
|
|
|
|
|
required_env=[],
|
|
|
|
|
|
hooks={"pre_run": [], "post_run": []},
|
|
|
|
|
|
shutdown="/exit",
|
|
|
|
|
|
idle={},
|
|
|
|
|
|
startup_timeout=30,
|
|
|
|
|
|
terminal={},
|
|
|
|
|
|
session_logs={},
|
|
|
|
|
|
)
|
|
|
|
|
|
assert backend.family == "claude"
|
|
|
|
|
|
|
|
|
|
|
|
def test_unknown_family_is_other(self):
|
|
|
|
|
|
backend = Backend(
|
|
|
|
|
|
name="random-xyz",
|
|
|
|
|
|
cli="xyz",
|
|
|
|
|
|
args=[],
|
|
|
|
|
|
required_env=[],
|
|
|
|
|
|
hooks={"pre_run": [], "post_run": []},
|
|
|
|
|
|
shutdown="/exit",
|
|
|
|
|
|
idle={},
|
|
|
|
|
|
startup_timeout=30,
|
|
|
|
|
|
terminal={},
|
|
|
|
|
|
session_logs={},
|
|
|
|
|
|
)
|
|
|
|
|
|
assert backend.family == "other"
|