test_backend.py 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156
  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. def test_loads_gemini_default_and_flash_variant(self, backends_dir):
  27. backend = load_backend("gemini", backends_dir)
  28. assert backend.name == "gemini"
  29. assert backend.family == "gemini"
  30. assert backend.model == "auto-gemini-3"
  31. flash_backend = load_backend("gemini-2-5-flash", backends_dir)
  32. assert flash_backend.name == "gemini-2-5-flash"
  33. assert flash_backend.family == "gemini"
  34. assert flash_backend.model == "gemini-2.5-flash"
  35. class TestBackendBuildCommand:
  36. def test_claude_build_command(self, backends_dir, monkeypatch):
  37. monkeypatch.setenv("SUPERPOWERS_ROOT", "/tmp/superpowers")
  38. backend = load_backend("claude", backends_dir)
  39. cmd = backend.build_command("/tmp/workdir")
  40. assert cmd[0] == "claude"
  41. assert "--plugin-dir" in cmd
  42. assert "/tmp/superpowers" in cmd
  43. def test_codex_build_command(self, backends_dir, monkeypatch):
  44. monkeypatch.setenv("SUPERPOWERS_ROOT", "/tmp/superpowers")
  45. backend = load_backend("codex", backends_dir)
  46. cmd = backend.build_command("/tmp/workdir")
  47. assert cmd[0] == "codex"
  48. class TestBackendEnvValidation:
  49. def test_missing_env_raises(self, backends_dir, monkeypatch):
  50. monkeypatch.delenv("ANTHROPIC_API_KEY", raising=False)
  51. monkeypatch.delenv("SUPERPOWERS_ROOT", raising=False)
  52. backend = load_backend("claude", backends_dir)
  53. with pytest.raises(EnvironmentError, match="ANTHROPIC_API_KEY"):
  54. backend.validate_env()
  55. class TestBackendIdleDetection:
  56. def test_ready_pattern_matches(self, backends_dir):
  57. backend = load_backend("claude", backends_dir)
  58. assert backend.is_ready_line("❯ ")
  59. assert backend.is_ready_line("Human: ")
  60. assert not backend.is_ready_line("Running tool...")
  61. class TestBackendModelExtraction:
  62. def test_extract_model_from_args(self, backends_dir, monkeypatch):
  63. monkeypatch.setenv("SUPERPOWERS_ROOT", "/tmp/sp")
  64. backend = load_backend("claude", backends_dir)
  65. assert backend.model == "opus"
  66. def test_no_model_flag_returns_none(self):
  67. backend = Backend(
  68. name="test",
  69. cli="test",
  70. args=["--foo", "bar"],
  71. required_env=[],
  72. hooks={"pre_run": [], "post_run": []},
  73. shutdown="/exit",
  74. idle={},
  75. startup_timeout=30,
  76. terminal={},
  77. session_logs={},
  78. )
  79. assert backend.model is None
  80. def test_extracts_from_short_m_flag(self):
  81. backend = Backend(
  82. name="test",
  83. cli="test",
  84. args=["-m", "gemini-2.5-flash"],
  85. required_env=[],
  86. hooks={"pre_run": [], "post_run": []},
  87. shutdown="/exit",
  88. idle={},
  89. startup_timeout=30,
  90. terminal={},
  91. session_logs={},
  92. )
  93. assert backend.model == "gemini-2.5-flash"
  94. class TestBackendFamily:
  95. def test_claude_backend_family(self, backends_dir, monkeypatch):
  96. monkeypatch.setenv("SUPERPOWERS_ROOT", "/tmp/sp")
  97. backend = load_backend("claude", backends_dir)
  98. assert backend.family == "claude"
  99. def test_codex_backend_family(self, backends_dir):
  100. backend = load_backend("codex", backends_dir)
  101. assert backend.family == "codex"
  102. def test_variant_name_preserves_family(self):
  103. backend = Backend(
  104. name="claude-opus-4-6",
  105. cli="claude",
  106. args=[],
  107. required_env=[],
  108. hooks={"pre_run": [], "post_run": []},
  109. shutdown="/exit",
  110. idle={},
  111. startup_timeout=30,
  112. terminal={},
  113. session_logs={},
  114. )
  115. assert backend.family == "claude"
  116. def test_unknown_family_is_other(self):
  117. backend = Backend(
  118. name="random-xyz",
  119. cli="xyz",
  120. args=[],
  121. required_env=[],
  122. hooks={"pre_run": [], "post_run": []},
  123. shutdown="/exit",
  124. idle={},
  125. startup_timeout=30,
  126. terminal={},
  127. session_logs={},
  128. )
  129. assert backend.family == "other"