"""Live Gitea round trip against the sandbox repo: push a branch, open a PR, comment, and prove the A3 reconciler reads external truth correctly. Cleans up after itself (closes the PR, deletes the branch) so repeated runs do not silt up the sandbox. """ import os import uuid from pathlib import Path import pytest from nisse_dev.envfile import load_env_file from nisse_dev.git import GitError, GitRepo, authenticated_url from nisse_dev.gitea import GiteaClient, pull_request_reconciler from nisse_dev.state import Intent load_env_file(Path(__file__).resolve().parents[2] / ".env") SENTINEL = "NISSE_GITEA_URL" ALSO_REQUIRED = ("NISSE_GITEA_TOKEN", "NISSE_GITEA_TEST_REPO") pytestmark = [ pytest.mark.integration, pytest.mark.skipif( not os.environ.get(SENTINEL), reason=f"no live Gitea configured ({SENTINEL} unset)", ), ] def intent_for(branch: str) -> Intent: return Intent( id=1, kind="open_pr", payload={"branch": branch}, status="pending", created_at="t", resolved_at=None, ) def test_push_open_pr_and_reconcile(tmp_path: Path) -> None: missing = [var for var in ALSO_REQUIRED if not os.environ.get(var)] assert not missing, ( f"{SENTINEL} is set, so the live Gitea path is expected to work, " f"but these are missing: {', '.join(missing)}" ) base_url = os.environ[SENTINEL] token = os.environ["NISSE_GITEA_TOKEN"] repo_path = os.environ["NISSE_GITEA_TEST_REPO"] client = GiteaClient.from_env() account = client.whoami() assert "testing" in account, ( f"live tests must run as a testing account, not {account}" ) branch = f"live-test-{uuid.uuid4().hex[:8]}" reconcile = pull_request_reconciler(client, repo_path) # Before the PR exists, external truth says the effect has not happened. assert reconcile(intent_for(branch)) is False repo = GitRepo(tmp_path / "work") repo.init() repo.configure_identity(account, f"{account}@example.test") (repo.root / f"{branch}.txt").write_text("live test artifact\n") assert repo.commit_all("Live test commit") is not None url = authenticated_url(base_url, repo_path, account, token) pr = None try: repo.push(url, branch, token=token) pr = client.open_pull_request( repo_path, head=branch, base="main", title=f"live test {branch}", body="Opened by the nisse-dev live test suite.", ) assert pr.head == branch client.post_comment(repo_path, pr.number, "live test comment") bodies = [c.body for c in client.list_comments(repo_path, pr.number)] assert "live test comment" in bodies # Now the same reconciler must see the effect and refuse to repeat it. assert reconcile(intent_for(branch)) is True # A4 is only real if the server enforces it. The agent's own token # must not be able to merge the PR it just opened, and must not be # able to push to the protected default branch at all. merged = client._http.post( f"/repos/{repo_path}/pulls/{pr.number}/merge", json={"Do": "merge"} ) assert merged.status_code >= 400, ( "the agent merged its own PR: branch protection is not enforcing " "the human gate" ) with pytest.raises(GitError): repo.push(url, "main", token=token) finally: if pr is not None: client.close_pull_request(repo_path, pr.number) repo.delete_remote_branch(url, branch, token=token) client.close()