Files
nisse/tests/integration/test_gitea_live.py
T
dongarandClaude Opus 5.5 761ebdb857
CI / checks (push) Skipped
CI / checks (pull_request) Failing after 55s
Run every command on the build VM over SSH (M1.3)
execution.SSHHost.execute is now the only way the agent runs anything
(L13). Commands travel as a script on SSH's stdin, so the secrets handed
to a call are never on a command line, and every value handed out is
redacted from the output. Timeouts kill the remote process group, and
an unreachable host is reported as such, not as a failed command.

Workspace and GitRepo run on the build VM. The clone uses the
repository's SSH URL, so no credential is stored in it (#89); fetches
and pushes get the push key over stdin, in a short-lived ssh-agent,
and PRs are still opened from the agent's host with its API token.
The bench runs on the build host too.

Tests reach "the build VM" through a throwaway local sshd (nisse_dev
.testing); CI installs openssh-server. The live push test now pushes
with the sandbox's deploy key (NISSE_PUSH_KEY).

Fixes #89.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PPQ14L3LikyefCMFBqEbKE
2026-10-02 21:39:46 +00:00

108 lines
3.8 KiB
Python

"""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()