← refacto-feature-scout
"""Taking a change back out — the thing a person needs most when they can't read the code."""
import subprocess
from pathlib import Path
import pytest
from scout import build as b
from scout.build import undo_idea
from scout.projects import Project
def git(repo: Path, *args: str) -> str:
return subprocess.run(["git", "-C", str(repo), *args], capture_output=True, text=True,
check=True).stdout.strip()
def make_repo(tmp_path: Path) -> Path:
repo = tmp_path / "repo"
repo.mkdir()
subprocess.run(["git", "init", "-q", "-b", "main", str(repo)], check=True)
git(repo, "config", "user.email", "t@t")
git(repo, "config", "user.name", "t")
(repo / "README.md").write_text("hello\n")
git(repo, "add", ".")
git(repo, "commit", "-q", "-m", "init")
return repo
def a_project(repo: Path) -> Project:
return Project(key="terminal", name="Test App", folder=repo, test_command="true")
def ship_something(repo: Path, store, name="feature.txt") -> tuple[int, str]:
"""Put a change into the project the way a completed build does, and note where things stood."""
before = git(repo, "rev-parse", "HEAD")
iid = store.add_idea("terminal", "A feature", "s", evidence=[{"url": "https://e", "note": ""}])
store.set_status(iid, "built", build_branch="scout/1-a-feature", merge_base_sha=before)
(repo / name).write_text("new thing\n")
git(repo, "add", ".")
git(repo, "commit", "-q", "-m", "Merge scout/1-a-feature (Feature Scout #1)")
store.set_status(iid, "shipped")
return iid, before
@pytest.fixture(autouse=True)
def scratch_dirs(tmp_path, monkeypatch):
monkeypatch.setattr(b, "WORKTREE_DIR", tmp_path / "wt")
monkeypatch.setattr(b, "LOG_DIR", tmp_path / "logs")
def test_a_change_can_be_taken_straight_back_out(tmp_path, store):
repo = make_repo(tmp_path)
iid, before = ship_something(repo, store)
result = undo_idea(a_project(repo), store.idea(iid), store)
assert result["ok"] and "taken back out" in result["message"]
assert "back exactly as it was" in result["message"]
assert git(repo, "rev-parse", "HEAD") == before
assert not (repo / "feature.txt").exists()
assert store.idea(iid)["status"] == "rejected"
def test_work_done_since_is_kept(tmp_path, store):
repo = make_repo(tmp_path)
iid, before = ship_something(repo, store)
(repo / "mine.txt").write_text("my own work\n")
git(repo, "add", ".")
git(repo, "commit", "-q", "-m", "something I did myself")
result = undo_idea(a_project(repo), store.idea(iid), store)
assert result["ok"] and "untouched" in result["message"]
assert (repo / "mine.txt").exists(), "the person's own work was destroyed"
assert not (repo / "feature.txt").exists(), "the change was not actually taken out"
assert git(repo, "rev-parse", "HEAD") != before
def test_nothing_is_touched_when_there_are_unsaved_changes(tmp_path, store):
repo = make_repo(tmp_path)
iid, _ = ship_something(repo, store)
(repo / "README.md").write_text("half-finished edit\n")
result = undo_idea(a_project(repo), store.idea(iid), store)
assert not result["ok"] and "unsaved changes" in result["message"]
assert (repo / "README.md").read_text() == "half-finished edit\n"
assert store.idea(iid)["status"] == "shipped"
def test_something_that_was_never_added_cannot_be_taken_back(tmp_path, store):
repo = make_repo(tmp_path)
iid = store.add_idea("terminal", "Not built", "s", evidence=[{"url": "https://e", "note": ""}])
result = undo_idea(a_project(repo), store.idea(iid), store)
assert not result["ok"] and "nothing to take back" in result["message"]
def test_a_build_records_where_things_stood_before_it_was_added(tmp_path, store):
"""Without this, there is nothing to go back to."""
repo = make_repo(tmp_path)
before = git(repo, "rev-parse", "HEAD")
iid = store.add_idea("terminal", "A feature", "s", evidence=[{"url": "https://e", "note": ""}])
git(repo, "checkout", "-q", "-b", "scout/1-a-feature")
(repo / "feature.txt").write_text("x\n")
git(repo, "add", ".")
git(repo, "commit", "-q", "-m", "built")
git(repo, "checkout", "-q", "main")
store.set_status(iid, "built", build_branch="scout/1-a-feature")
b.ship_idea(a_project(repo), store.idea(iid), store, tester=lambda p, c: (True, "ok"))
assert store.idea(iid)["merge_base_sha"] == before
assert store.idea(iid)["status"] == "shipped"
assert undo_idea(a_project(repo), store.idea(iid), store)["ok"]
assert git(repo, "rev-parse", "HEAD") == before