test_backend.py 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145
  1. from pathlib import Path
  2. import pytest
  3. from drill.backend import Backend, load_backend
  4. @pytest.fixture
  5. def backends_dir():
  6. return Path(__file__).parent.parent / "backends"
  7. class TestLoadBackend:
  8. def test_loads_claude_backend(self, backends_dir):
  9. backend = load_backend("claude", backends_dir)
  10. assert backend.name == "claude"
  11. assert backend.cli == "claude"
  12. assert "--dangerously-skip-permissions" in backend.args
  13. def test_loads_codex_backend(self, backends_dir):
  14. backend = load_backend("codex", backends_dir)
  15. assert backend.name == "codex"
  16. assert backend.cli == "codex"
  17. def test_unknown_backend_raises(self, backends_dir):
  18. with pytest.raises(FileNotFoundError):
  19. load_backend("nonexistent", backends_dir)
  20. def test_loads_claude_opus_4_6_variant(self, backends_dir, monkeypatch):
  21. monkeypatch.setenv("SUPERPOWERS_ROOT", "/tmp/sp")
  22. backend = load_backend("claude-opus-4-6", backends_dir)
  23. assert backend.name == "claude-opus-4-6"
  24. assert backend.family == "claude"
  25. assert backend.model == "claude-opus-4-6"
  26. class TestBackendBuildCommand:
  27. def test_claude_build_command(self, backends_dir, monkeypatch):
  28. monkeypatch.setenv("SUPERPOWERS_ROOT", "/tmp/superpowers")
  29. backend = load_backend("claude", backends_dir)
  30. cmd = backend.build_command("/tmp/workdir")
  31. assert cmd[0] == "claude"
  32. assert "--plugin-dir" in cmd
  33. assert "/tmp/superpowers" in cmd
  34. def test_codex_build_command(self, backends_dir, monkeypatch):
  35. monkeypatch.setenv("SUPERPOWERS_ROOT", "/tmp/superpowers")
  36. backend = load_backend("codex", backends_dir)
  37. cmd = backend.build_command("/tmp/workdir")
  38. assert cmd[0] == "codex"
  39. class TestBackendEnvValidation:
  40. def test_missing_env_raises(self, backends_dir, monkeypatch):
  41. monkeypatch.delenv("ANTHROPIC_API_KEY", raising=False)
  42. monkeypatch.delenv("SUPERPOWERS_ROOT", raising=False)
  43. backend = load_backend("claude", backends_dir)
  44. with pytest.raises(EnvironmentError, match="ANTHROPIC_API_KEY"):
  45. backend.validate_env()
  46. class TestBackendIdleDetection:
  47. def test_ready_pattern_matches(self, backends_dir):
  48. backend = load_backend("claude", backends_dir)
  49. assert backend.is_ready_line("❯ ")
  50. assert backend.is_ready_line("Human: ")
  51. assert not backend.is_ready_line("Running tool...")
  52. class TestBackendModelExtraction:
  53. def test_extract_model_from_args(self, backends_dir, monkeypatch):
  54. monkeypatch.setenv("SUPERPOWERS_ROOT", "/tmp/sp")
  55. backend = load_backend("claude", backends_dir)
  56. assert backend.model == "opus"
  57. def test_no_model_flag_returns_none(self):
  58. backend = Backend(
  59. name="test",
  60. cli="test",
  61. args=["--foo", "bar"],
  62. required_env=[],
  63. hooks={"pre_run": [], "post_run": []},
  64. shutdown="/exit",
  65. idle={},
  66. startup_timeout=30,
  67. terminal={},
  68. session_logs={},
  69. )
  70. assert backend.model is None
  71. def test_extracts_from_short_m_flag(self):
  72. backend = Backend(
  73. name="test",
  74. cli="test",
  75. args=["-m", "gemini-2.5-flash"],
  76. required_env=[],
  77. hooks={"pre_run": [], "post_run": []},
  78. shutdown="/exit",
  79. idle={},
  80. startup_timeout=30,
  81. terminal={},
  82. session_logs={},
  83. )
  84. assert backend.model == "gemini-2.5-flash"
  85. class TestBackendFamily:
  86. def test_claude_backend_family(self, backends_dir, monkeypatch):
  87. monkeypatch.setenv("SUPERPOWERS_ROOT", "/tmp/sp")
  88. backend = load_backend("claude", backends_dir)
  89. assert backend.family == "claude"
  90. def test_codex_backend_family(self, backends_dir):
  91. backend = load_backend("codex", backends_dir)
  92. assert backend.family == "codex"
  93. def test_variant_name_preserves_family(self):
  94. backend = Backend(
  95. name="claude-opus-4-6",
  96. cli="claude",
  97. args=[],
  98. required_env=[],
  99. hooks={"pre_run": [], "post_run": []},
  100. shutdown="/exit",
  101. idle={},
  102. startup_timeout=30,
  103. terminal={},
  104. session_logs={},
  105. )
  106. assert backend.family == "claude"
  107. def test_unknown_family_is_other(self):
  108. backend = Backend(
  109. name="random-xyz",
  110. cli="xyz",
  111. args=[],
  112. required_env=[],
  113. hooks={"pre_run": [], "post_run": []},
  114. shutdown="/exit",
  115. idle={},
  116. startup_timeout=30,
  117. terminal={},
  118. session_logs={},
  119. )
  120. assert backend.family == "other"