CI / checks (push) Skipped
CI / checks (pull_request) Successful in 1m15s
On the first live run the executor committed its milestone itself, as its plan asked. The publisher only published a commit it made, found a clean tree, reported "no changes to publish", and left the agent in EXECUTING, where every message is answered "busy". - publish(): with nothing left to commit, publish anyway when the branch has commits origin/<base> lacks; report no changes only when it has none. - push_followup(): same for review fixes, comparing HEAD with what the remote branch holds (ls-remote; pushes go by URL, so there is no tracking ref). - Nothing to publish now gates to AWAIT_INPUT, and the reply returns to the still-approved milestone plan. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PPQ14L3LikyefCMFBqEbKE
88 lines
2.9 KiB
Python
88 lines
2.9 KiB
Python
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
from nisse_dev.git import GitError, GitRepo, _scrub, authenticated_url
|
|
|
|
|
|
@pytest.fixture
|
|
def repo(tmp_path: Path) -> GitRepo:
|
|
r = GitRepo(tmp_path / "work")
|
|
r.init()
|
|
r.configure_identity("nisse-dev-testing", "nisse@example.test")
|
|
return r
|
|
|
|
|
|
def test_init_creates_repo_on_default_branch(repo: GitRepo) -> None:
|
|
assert repo.current_branch() == "main"
|
|
|
|
|
|
def test_commit_all_returns_sha_and_branch_switching(repo: GitRepo) -> None:
|
|
(repo.root / "a.txt").write_text("a")
|
|
sha = repo.commit_all("first")
|
|
assert sha is not None
|
|
assert sha == repo.head_sha()
|
|
|
|
repo.checkout_new_branch("milestone-1")
|
|
assert repo.current_branch() == "milestone-1"
|
|
|
|
|
|
def test_commit_with_nothing_staged_returns_none(repo: GitRepo) -> None:
|
|
(repo.root / "a.txt").write_text("a")
|
|
repo.commit_all("first")
|
|
# An empty milestone is a fact to report, not an error to raise.
|
|
assert repo.commit_all("nothing changed") is None
|
|
|
|
|
|
def test_has_changes_tracks_the_worktree(repo: GitRepo) -> None:
|
|
assert not repo.has_changes()
|
|
(repo.root / "a.txt").write_text("a")
|
|
assert repo.has_changes()
|
|
|
|
|
|
def test_authenticated_url_shape() -> None:
|
|
url = authenticated_url("https://gitea.test/", "o/r", "bot", "secret")
|
|
assert url == "https://bot:secret@gitea.test/o/r.git"
|
|
|
|
|
|
def test_push_failure_does_not_leak_the_token(repo: GitRepo) -> None:
|
|
(repo.root / "a.txt").write_text("a")
|
|
repo.commit_all("first")
|
|
url = authenticated_url("https://127.0.0.1:1", "o/r", "bot", "sup3rsecret")
|
|
with pytest.raises(GitError) as excinfo:
|
|
repo.push(url, "main", token="sup3rsecret")
|
|
# git redacts credentials from its own messages, but that is git's
|
|
# behaviour, not ours; the scrub below is the guarantee we control.
|
|
assert "sup3rsecret" not in str(excinfo.value)
|
|
|
|
|
|
def test_scrub_removes_the_token_when_git_does_echo_it() -> None:
|
|
assert _scrub("failed to push to https://bot:s3cret@h/o/r.git", "s3cret") == (
|
|
"failed to push to https://bot:***@h/o/r.git"
|
|
)
|
|
assert _scrub("no secret here", None) == "no secret here"
|
|
|
|
|
|
def test_git_error_on_bad_command(tmp_path: Path) -> None:
|
|
repo = GitRepo(tmp_path / "not-a-repo")
|
|
repo.root.mkdir()
|
|
with pytest.raises(GitError):
|
|
repo.head_sha()
|
|
|
|
|
|
def test_commits_since_and_remote_sha_against_a_local_remote(
|
|
repo: GitRepo, tmp_path: Path
|
|
) -> None:
|
|
remote = GitRepo(tmp_path / "remote.git")
|
|
remote.root.mkdir()
|
|
remote._run("init", "-q", "--bare")
|
|
(repo.root / "a.txt").write_text("a")
|
|
base = repo.commit_all("base")
|
|
repo.push(str(remote.root), "main")
|
|
repo._run("update-ref", "refs/remotes/origin/main", "HEAD")
|
|
assert repo.remote_sha(str(remote.root), "main") == base
|
|
assert repo.remote_sha(str(remote.root), "absent") is None
|
|
(repo.root / "b.txt").write_text("b")
|
|
repo.commit_all("ahead")
|
|
assert repo.commits_since("origin/main") == 1
|