atlas-iac/testing/tests/test_hermes_handoff_ephemeral.py

338 lines
12 KiB
Python

"""Contracts for the strictly bounded, self-cleaning armed mode."""
from __future__ import annotations
import json
import subprocess
from pathlib import Path
import pytest
from testing.tests.test_hermes_handoff_support import (
FakeClock,
FakeSpawn,
load_handoff_module,
)
ephemeral = load_handoff_module("hermes_handoff_ephemeral")
exec_module = load_handoff_module("hermes_handoff_exec")
policy = load_handoff_module("hermes_handoff_policy")
model = load_handoff_module("hermes_handoff_model")
TOKEN = "acceptance-20260817a"
HEAD = "8f005458282269ba5c07941814e4237f5d4cf3ac"
MAIN = "1" * 40
VANTAGE = exec_module.operator_vantage()
def request(**overrides):
fields = {
"repo": policy.EXPECTED_REPO,
"remote": policy.EXPECTED_REMOTE,
"token": TOKEN,
"confirmation": ephemeral.CONFIRMATION,
"base": policy.EXPECTED_BASE,
"expected_head": HEAD,
"expected_base_sha": MAIN,
}
fields.update(overrides)
return ephemeral.ArmRequest(**fields)
def completed(stdout: str = "", stderr: str = "", returncode: int = 0):
return subprocess.CompletedProcess(
args=[], returncode=returncode, stdout=stdout, stderr=stderr
)
def armed_runner(results: list, mode=policy.ARMED):
spawn = FakeSpawn(results)
runner = exec_module.Runner(
mode=mode,
clock=FakeClock(),
spawn=spawn,
environ={},
deadline_seconds=3600,
attestor=lambda command, _environment: exec_module.Attestation(
command, "a" * 64
),
)
return runner, spawn
def by_id(results: list) -> dict:
return {result.spec.id: result for result in results}
def pr_payload(state="open", number=42):
return {
"number": number,
"state": state,
"draft": True,
"merged": False,
"base": {"ref": "main", "sha": MAIN},
"head": {"ref": request().ref, "sha": HEAD},
}
def linked_worktree(tmp_path: Path) -> Path:
root = tmp_path / "worktree"
common = tmp_path / "repo.git"
gitdir = common / "worktrees" / "acceptance"
gitdir.mkdir(parents=True)
(root).mkdir()
(root / ".git").write_text(f"gitdir: {gitdir}\n", encoding="utf-8")
(gitdir / "commondir").write_text("../..\n", encoding="utf-8")
(gitdir / "HEAD").write_text(
f"ref: refs/heads/{ephemeral.EXPECTED_HEAD_REF}\n", encoding="utf-8"
)
ref = common / "refs" / "heads" / ephemeral.EXPECTED_HEAD_REF
ref.parent.mkdir(parents=True)
ref.write_text(f"{HEAD}\n", encoding="utf-8")
remote_ref = common / "refs" / "remotes" / "origin" / "main"
remote_ref.parent.mkdir(parents=True)
remote_ref.write_text(f"{MAIN}\n", encoding="utf-8")
(common / "config").write_text(
f'[remote "origin"]\n\turl = {ephemeral.EXPECTED_REMOTE_URL}\n',
encoding="utf-8",
)
return root
@pytest.mark.parametrize(
"ref", ["main", "master", "refs/heads/main", "HEAD", "feature/x", ""]
)
def test_only_the_unique_ephemeral_ref_is_allowed(ref: str) -> None:
with pytest.raises(ephemeral.ArmingError):
ephemeral.assert_push_target_allowed(ref)
ephemeral.assert_push_target_allowed(request().ref)
@pytest.mark.parametrize(
"overrides",
[
{"confirmation": "wrong"},
{"repo": "atlas/other"},
{"remote": "upstream"},
{"base": "develop"},
{"token": "short"},
{"expected_head": "bad"},
{"expected_base_sha": "bad"},
],
)
def test_preflight_rejects_bad_inputs_before_any_runner_exists(
tmp_path: Path, overrides: dict
) -> None:
with pytest.raises(ephemeral.ArmingError):
ephemeral.preflight(
request(**overrides), policy.EXPECTED_REPO, linked_worktree(tmp_path)
)
def test_preflight_attests_the_actual_linked_worktree(tmp_path: Path) -> None:
ephemeral.preflight(request(), policy.EXPECTED_REPO, linked_worktree(tmp_path))
root = linked_worktree(tmp_path / "wrong")
(Path((root / ".git").read_text().split(": ", 1)[1].strip()) / "HEAD").write_text(
"ref: refs/heads/other\n", encoding="utf-8"
)
with pytest.raises(ephemeral.ArmingError, match="existing PR #19"):
ephemeral.preflight(request(), policy.EXPECTED_REPO, root)
def test_worktree_attestation_rejects_each_malformed_local_source(
tmp_path: Path,
) -> None:
roots = []
empty = tmp_path / "empty"
empty.mkdir()
roots.append(empty)
bad_marker = linked_worktree(tmp_path / "marker")
(bad_marker / ".git").write_text("not-a-gitdir\n", encoding="utf-8")
roots.append(bad_marker)
wrong_sha = linked_worktree(tmp_path / "sha")
gitdir = Path((wrong_sha / ".git").read_text().split(": ", 1)[1].strip())
common = (gitdir / (gitdir / "commondir").read_text().strip()).resolve()
(common / "refs" / "heads" / ephemeral.EXPECTED_HEAD_REF).write_text("0" * 40)
roots.append(wrong_sha)
wrong_main = linked_worktree(tmp_path / "main")
gitdir = Path((wrong_main / ".git").read_text().split(": ", 1)[1].strip())
common = (gitdir / (gitdir / "commondir").read_text().strip()).resolve()
(common / "refs" / "remotes" / "origin" / "main").write_text("0" * 40)
roots.append(wrong_main)
bad_config = linked_worktree(tmp_path / "config")
gitdir = Path((bad_config / ".git").read_text().split(": ", 1)[1].strip())
common = (gitdir / (gitdir / "commondir").read_text().strip()).resolve()
(common / "config").write_text("[broken\n", encoding="utf-8")
roots.append(bad_config)
wrong_remote = linked_worktree(tmp_path / "remote")
gitdir = Path((wrong_remote / ".git").read_text().split(": ", 1)[1].strip())
common = (gitdir / (gitdir / "commondir").read_text().strip()).resolve()
(common / "config").write_text('[remote "origin"]\nurl=https://example.invalid/x\n')
roots.append(wrong_remote)
for root in roots:
with pytest.raises((ephemeral.ArmingError, FileNotFoundError)):
ephemeral.preflight(request(), policy.EXPECTED_REPO, root)
oversized = tmp_path / "oversized"
oversized.write_text("x" * 5, encoding="utf-8")
with pytest.raises(ephemeral.ArmingError):
ephemeral._bounded_read(oversized, 4)
def test_guard_failure_stops_before_runner_calls(monkeypatch) -> None:
monkeypatch.setattr(ephemeral, "assert_push_target_allowed", lambda _ref: None)
runner, spawn = armed_runner([])
results = by_id(ephemeral.run_armed(runner, VANTAGE, request()))
assert results["ephemeral.protected-branch-refusal"].status == model.FAIL
assert all(
result.status in {model.FAIL, model.NOT_RUN} for result in results.values()
)
assert spawn.calls == []
def test_full_armed_run_validates_and_removes_exact_artifacts() -> None:
ref_line = f"{HEAD}\trefs/heads/{request().ref}\n"
runner, spawn = armed_runner(
[
completed(),
completed(stdout=f"{HEAD}\n"),
completed(),
completed(stdout=ref_line),
completed(stdout=json.dumps(pr_payload())),
completed(stdout=json.dumps([pr_payload()])),
completed(stdout=json.dumps(pr_payload(state="closed"))),
completed(),
completed(),
completed(stdout=json.dumps(pr_payload(state="closed"))),
]
)
results = by_id(ephemeral.run_armed(runner, VANTAGE, request()))
assert all(result.status == model.PASS for result in results.values())
push = next(call for call in spawn.calls if call["argv"][1] == "push")
assert push["argv"][-1] == f"HEAD:refs/heads/{request().ref}"
assert all(result.spec.mandatory for result in results.values())
def test_existing_or_unverifiable_ref_never_reaches_a_write() -> None:
for first in (completed(stdout="abc\trefs/heads/x\n"), completed(returncode=128)):
runner, spawn = armed_runner([first])
results = by_id(ephemeral.run_armed(runner, VANTAGE, request()))
assert results["ephemeral.feature-branch-push"].status == model.FAIL
assert not any(
call["argv"][1] in {"push", "POST", "PATCH", "DELETE"}
for call in spawn.calls
)
def test_failed_or_ambiguous_create_is_discovered_and_cleanup_still_runs() -> None:
ref_line = f"{HEAD}\trefs/heads/{request().ref}\n"
runner, _ = armed_runner(
[
completed(),
completed(stdout=f"{HEAD}\n"),
completed(),
completed(stdout=ref_line),
completed(returncode=1),
completed(stdout=json.dumps([pr_payload()])),
completed(stdout=json.dumps(pr_payload(state="closed"))),
completed(),
completed(),
completed(stdout=json.dumps(pr_payload(state="closed"))),
]
)
results = by_id(ephemeral.run_armed(runner, VANTAGE, request()))
assert results["ephemeral.draft-pull-request"].status == model.PASS
assert results["ephemeral.cleanup-verified"].status == model.PASS
def test_pull_request_parsers_reject_every_malformed_or_ambiguous_shape() -> None:
assert ephemeral._pr_problems([], request()) == ["pull request is not an object"]
malformed = pr_payload()
malformed.update({"number": True, "draft": "yes", "base": None})
malformed["head"] = {"ref": "other", "sha": "bad"}
problems = ephemeral._pr_problems(malformed, request())
assert {"number is malformed", "draft does not match", "base is malformed"} <= set(
problems
)
assert ephemeral._discover(exec_module.Outcome((), "x", returncode=1), request())[1]
for body in (
"not-json",
"{}",
"[null]",
json.dumps([{"head": {"ref": "other"}, "base": {}}]),
):
numbers, error = ephemeral._discover(
exec_module.Outcome((), "x", 0, body), request()
)
assert numbers == [] and error
invalid = pr_payload()
invalid["draft"] = False
assert ephemeral._discover(
exec_module.Outcome((), "x", 0, json.dumps([invalid])), request()
)[1]
assert (
"discovered 0"
in ephemeral._discover(exec_module.Outcome((), "x", 0, "[]"), request())[1]
)
@pytest.mark.parametrize(
"delete_result,remaining,closed",
[
(completed(returncode=1), completed(), pr_payload(state="closed")),
(completed(), completed(returncode=128), pr_payload(state="closed")),
(completed(), completed(), {}),
(completed(), completed(), pr_payload(state="open")),
],
)
def test_cleanup_fails_closed_on_partial_or_malformed_evidence(
delete_result, remaining, closed
) -> None:
ref_line = f"{HEAD}\trefs/heads/{request().ref}\n"
runner, _ = armed_runner(
[
completed(),
completed(stdout=f"{HEAD}\n"),
completed(),
completed(stdout=ref_line),
completed(),
completed(stdout=json.dumps([pr_payload()])),
completed(stdout=json.dumps(pr_payload(state="closed"))),
delete_result,
remaining,
completed(stdout=json.dumps(closed)),
]
)
cleanup = by_id(ephemeral.run_armed(runner, VANTAGE, request()))[
"ephemeral.cleanup-verified"
]
assert cleanup.status == model.FAIL
def test_cleanup_fails_when_close_or_verification_commands_are_bad_json_or_failures() -> (
None
):
api = "/api/v1/repos/atlas/titan-iac"
for scripted in (
[completed(returncode=1), completed(), completed(), completed(returncode=1)],
[completed(stdout="bad"), completed(), completed(), completed(stdout="bad")],
[
completed(stdout=json.dumps(pr_payload())),
completed(),
completed(),
completed(stdout=json.dumps(pr_payload(state="closed"))),
],
):
runner, _ = armed_runner(scripted)
result = ephemeral._cleanup(runner, VANTAGE, request(), api, [42])
assert result.status == model.FAIL
def test_read_only_runner_cannot_reach_push() -> None:
runner, spawn = armed_runner(
[completed(), completed(stdout=f"{HEAD}\n"), completed(), completed()],
mode=policy.READ_ONLY,
)
results = by_id(ephemeral.run_armed(runner, VANTAGE, request()))
assert results["ephemeral.feature-branch-push"].status == model.FAIL
assert not any(call["argv"][1] == "push" for call in spawn.calls)