A repository per test run instead of the shared sandbox
CI / checks (push) Skipped
CI / checks (pull_request) Successful in 5m17s

Every live test that touches a repository gets one of its own from
e2e_support.fresh_repo, made by the test operator as an operator makes a
project's (D4, D8): seeded from tests/integration/seed/ (the sandbox's
package, without past runs' leftovers), the agent's account a collaborator
with write access, main protected (pushes only by the owner, an approval to
merge), and a deploy key generated for the run. It is deleted afterwards
(NISSE_E2E_KEEP_REPOS keeps it); scripts/e2e-cleanup.sh deletes leftovers.

- Both e2e fixtures use it; per-run base branches are gone. The merge
  helper approves first, as the protection now requires.
- test_gitea_live uses it, so CI needs NISSE_OPERATOR_TOKEN and no longer
  writes the sandbox deploy key (NISSE_PUSH_KEY).
- A live test of the helper: seeded, shared with the agent, protected
  against it, deleted.
- nisse-test.yaml's project entry is a placeholder; pytest and ruff skip
  the seed.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PPQ14L3LikyefCMFBqEbKE
This commit is contained in:
dongarandClaude Opus 5.5 committed 2026-10-06 05:10:27 +00:00
1 parent 6c962e1ed9
commit 679ace1fd8
18 files changed
+379 -55

No files matched your search

+22 -12
View File
@@ -1,19 +1,18 @@
"""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.
"""Live Gitea round trip in a repository of the run's own: push a branch,
open a PR, comment, and prove the L8 reconciler reads external truth
correctly, and that protection holds against the agent (D4).
"""
import uuid
from pathlib import Path
import pytest
from e2e_support import fresh_repo, operator_gitea
from nisse_dev.agent import push_credential, remote_url
from nisse_dev.agent import remote_url
from nisse_dev.config import AgentConfig
from nisse_dev.execution import SSHHost
from nisse_dev.git import GitError, GitRepo
from nisse_dev.git import GitError, GitRepo, SSHKey
from nisse_dev.gitea import GiteaClient, pull_request_reconciler
from nisse_dev.state import Intent
@@ -34,9 +33,21 @@ def intent_for(branch: str) -> Intent:
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
with fresh_repo(
operator_gitea(live_config.gitea_url),
uuid.uuid4().hex[:8],
tmp_path / "keys",
agent=client.whoami(),
) as fresh:
_round_trip(
tmp_path, client, ssh_host, fresh.name, SSHKey(fresh.push_key.read_text())
)
def _round_trip(
tmp_path: Path, client: GiteaClient, ssh_host: SSHHost, repo_path: str, key: SSHKey
) -> None:
account = client.whoami()
assert "testing" in account, (
f"live tests must run as a testing account, not {account}"
@@ -49,12 +60,12 @@ def test_push_open_pr_and_reconcile(
assert reconcile(intent_for(branch)) is False
# Pushed the way the agent does: from the build VM (here the throwaway
# sshd), with the push credential the config names, never the API token.
key = push_credential(live_config)
assert key is not None, "nisse-test.yaml names no push credential (D4)"
# sshd), with the repository's deploy key, never the API token.
url = remote_url(client, repo_path, key)
repo = GitRepo(ssh_host, tmp_path / "work", known_hosts=str(tmp_path / "kh"))
repo.init()
repo._git("fetch", "-q", url, "main", credential=key)
repo._git("checkout", "-q", "-B", "work", "FETCH_HEAD")
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
@@ -93,5 +104,4 @@ def test_push_open_pr_and_reconcile(
finally:
if pr is not None:
client.close_pull_request(repo_path, pr.number)
repo.delete_remote_branch(url, branch, credential=key)
client.close()