"""Live Gitea round trip against the sandbox repo: push a branch, open a PR, comment, and prove the L8 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.config import AgentConfig from nisse_dev.execution import SSHHost from nisse_dev.git import GitError, GitRepo from nisse_dev.gitea import GiteaClient, pull_request_reconciler from nisse_dev.state import Intent pytestmark = pytest.mark.integration 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 push_key() -> str: """The sandbox's deploy key with write access: what the agent pushes with (D4). NISSE_PUSH_KEY holds the private key itself (CI) or, since a .env line can't hold one, the path to it (local runs).""" value = os.environ.get("NISSE_PUSH_KEY", "") assert value.strip(), "NISSE_PUSH_KEY is not set: the sandbox's deploy key" if "PRIVATE KEY" not in value and Path(value).expanduser().is_file(): value = Path(value).expanduser().read_text() return value if value.endswith("\n") else value + "\n" def test_push_open_pr_and_reconcile( tmp_path: Path, live_config: AgentConfig, gitea: GiteaClient, ssh_host: SSHHost ) -> None: repo_path = live_config.gitea_repo # the sandbox client = gitea 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 # Pushed the way the agent does: from the build VM (here the throwaway # sshd), over SSH, with the deploy key and never the API token. key = push_key() url = client.ssh_url(repo_path) repo = GitRepo(ssh_host, tmp_path / "work", known_hosts=str(tmp_path / "kh")) 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 pr = None try: repo.push(url, branch, key=key) 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 # D4 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 its push key # 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", key=key) finally: if pr is not None: client.close_pull_request(repo_path, pr.number) repo.delete_remote_branch(url, branch, key=key) client.close()