Files

122 lines
4.8 KiB
Python
Raw Permalink Normal View History

"""Pure-function tests for parametrize-base-branch (v1.1 task 5)."""
import importlib.util
import json
import sys
from pathlib import Path
from unittest.mock import patch
spec = importlib.util.spec_from_file_location(
"st", str(Path(__file__).resolve().parent.parent / "scripts" / "status.py")
)
st = importlib.util.module_from_spec(spec)
spec.loader.exec_module(st)
class TestBaseBranch:
def test_default_main(self):
assert st._base_branch(None) == "main"
assert st._base_branch({}) == "main"
assert st._base_branch({"blast_radius": {}}) == "main"
assert st._base_branch({"blast_radius": {"base_branch": None}}) == "main"
def test_explicit_value(self):
assert st._base_branch({"blast_radius": {"base_branch": "trunk"}}) == "trunk"
assert st._base_branch({"blast_radius": {"base_branch": "develop"}}) == "develop"
assert st._base_branch({"blast_radius": {"base_branch": "master"}}) == "master"
def test_empty_string_with_warning(self):
assert st._base_branch({"blast_radius": {"base_branch": ""}}) == "main"
def test_non_string_with_warning(self):
assert st._base_branch({"blast_radius": {"base_branch": 42}}) == "42"
assert st._base_branch({"blast_radius": {"base_branch": True}}) == "True"
class TestDriftGateBranch:
"""Verify _gate_worktree_drift uses the configured base_branch."""
def make_state(self, worktree_path="/tmp/wt"):
return {"worktree_path": worktree_path}
def make_cfg(self, base_branch="main", file_scope=None):
fs = file_scope or ["src/", "tests/"]
cfg = {"blast_radius": {"file_scope": list(fs)}}
cfg["blast_radius"]["base_branch"] = base_branch
return cfg
@patch("subprocess.run")
def test_uses_configured_branch(self, mock_run, tmp_path):
mock_run.return_value.returncode = 0
mock_run.return_value.stdout = ""
mock_run.return_value.stderr = ""
cfg = self.make_cfg(base_branch="trunk")
st._gate_worktree_drift({"worktree_path": str(tmp_path)}, cfg, None)
call = mock_run.call_args
assert call is not None
args = call[0][0]
assert "trunk...HEAD" in args, f"expected 'trunk...HEAD' in {args}"
@patch("subprocess.run")
def test_falls_back_to_main(self, mock_run, tmp_path):
mock_run.return_value.returncode = 0
mock_run.return_value.stdout = ""
mock_run.return_value.stderr = ""
cfg = self.make_cfg(base_branch="main")
st._gate_worktree_drift({"worktree_path": str(tmp_path)}, cfg, None)
call = mock_run.call_args
args = call[0][0]
assert "main...HEAD" in args, f"expected 'main...HEAD' in {args}"
@patch("subprocess.run")
def test_bad_revision_skips_with_warning(self, mock_run, tmp_path):
mock_run.return_value.returncode = 128
mock_run.return_value.stdout = ""
mock_run.return_value.stderr = "fatal: bad revision 'not_a_branch'"
rv = st._gate_worktree_drift(
{"worktree_path": str(tmp_path)},
self.make_cfg(base_branch="not_a_branch"),
None)
assert rv is None
@patch("subprocess.run")
def test_drift_detected(self, mock_run, tmp_path):
mock_run.return_value.returncode = 0
mock_run.return_value.stdout = "src/valid.py\nextraneous.txt\n"
mock_run.return_value.stderr = ""
rv = st._gate_worktree_drift(
{"worktree_path": str(tmp_path)}, self.make_cfg(), None)
assert rv is not None
assert rv["halt_reason"] == "drift_detected"
assert "extraneous.txt" in rv["out_of_scope_files"]
@patch("subprocess.run")
def test_drift_in_scope_ok(self, mock_run, tmp_path):
mock_run.return_value.returncode = 0
mock_run.return_value.stdout = "src/valid.py\n"
mock_run.return_value.stderr = ""
rv = st._gate_worktree_drift(
{"worktree_path": str(tmp_path)}, self.make_cfg(), None)
assert rv is None
def test_no_worktree_returns_none(self):
assert st._gate_worktree_drift({}, {}, None) is None
def test_missing_worktree_dir_returns_none(self):
rv = st._gate_worktree_drift(
{"worktree_path": "/nonexistent_path_xyz"},
self.make_cfg(), None)
assert rv is None
def test_empty_file_scope_returns_none(self, tmp_path):
cfg = {"blast_radius": {"file_scope": [], "base_branch": "main"}}
rv = st._gate_worktree_drift(
{"worktree_path": str(tmp_path)}, cfg, None)
assert rv is None
def test_template_includes_base_branch(self):
tpl_path = (Path(__file__).resolve().parent.parent /
"templates" / "loops" / "self-improvement" / "loop.json")
tpl_cfg = json.loads(tpl_path.read_text())
assert tpl_cfg["blast_radius"]["base_branch"] == "main"