Files
cli/tests/test_installer.py
zeekay b31ae4c306 dev: Gen 7846eed; installs gen from v tags, checks what gh can check
hanzo-gen moves to 7846eed: every child it starts gets only the absolute
PATH entries, a signal during an agent's startup no longer orphans it,
and a run home is swept only when no session holds its lock.

install.sh installs a tool with a signer from vX.Y.Z tags only, the form
its release workflow runs on and its ruleset guards. It asks gh whether
it has `attestation verify --source-ref` before using it: Ubuntu's gh
2.45.0 has no attestation command, and the install died calling that a
tamper. Now a gh that cannot check says so, with its version and the
command that checks, and only a verification that ran and failed refuses
the install, with gh's reason.

link starts tmux, ttyd and ps by full path through gen's exe::command,
and upgrade starts npm, uv and the agents' own upgraders the same way.
2026-10-16 10:45:00 -07:00

283 lines
13 KiB
Python

"""Exercise the standalone installer with fake GitHub answers and real archives."""
import hashlib
import io
import json
import os
from pathlib import Path
import subprocess
import tarfile
import tempfile
import unittest
INSTALLER = Path(__file__).resolve().parents[1] / "install.sh"
ARCHIVE = "hanzo-mcp-linux-amd64.tar.gz"
# A stand-in for curl that answers from files under $FIXTURES:
# refs git's ref advertisement for the repo
# releases/<tag>/<asset> a published release asset
# api-blocked present: api.github.com answers 403, as it does
# once an address has spent its 60 anonymous calls
# Anything missing answers 404, and -f turns 4xx into exit 22, as curl does.
FAKE_CURL = r'''#!/usr/bin/env python3
import json, os, pathlib, sys
root = pathlib.Path(os.environ["FIXTURES"])
args = sys.argv[1:]
url = next(a for a in args if a.startswith("https://"))
headers = [args[i + 1] for i, a in enumerate(args) if a == "-H"]
with (root / "requests").open("a") as out: out.write(json.dumps([url, headers]) + "\n")
releases = root / "releases"
published = sorted((t.name, a.name) for t in releases.glob("*") for a in t.glob("*"))
def body():
if url.startswith("https://api.github.com/"):
if (root / "api-blocked").exists(): return None
path = url.split("/repos/hanzoai/mcp/", 1)[1]
if path.startswith("releases/tags/"):
tag = path.rsplit("/", 1)[1]
names = [(100 + i, n) for i, (t, n) in enumerate(published) if t == tag]
if not names: return None
return json.dumps({"tag_name": tag, "assets": [
{"id": i, "name": n} for i, n in names]}, indent=2).encode()
if path.startswith("releases/assets/"):
tag, name = published[int(path.rsplit("/", 1)[1]) - 100]
return (releases / tag / name).read_bytes()
return None
if url.startswith("https://github.com/hanzoai/mcp.git/info/refs"):
refs = root / "refs"
return refs.read_bytes() if refs.exists() else None
prefix = "https://github.com/hanzoai/mcp/releases/download/"
if url.startswith(prefix):
path = root / "releases" / url[len(prefix):]
return path.read_bytes() if path.is_file() else None
return None
data = body()
if data is None: sys.exit(22)
if "-I" in args: sys.exit(0)
if "-o" in args: pathlib.Path(args[args.index("-o") + 1]).write_bytes(data)
else: sys.stdout.buffer.write(data)
'''
def pkt(line):
return f"{len(line) + 4:04x}{line}"
class InstallerTests(unittest.TestCase):
def setUp(self):
self.directory = tempfile.TemporaryDirectory()
self.addCleanup(self.directory.cleanup)
self.root = Path(self.directory.name)
self.bin = self.root / "bin"
self.bin.mkdir()
self.prefix = self.root / "installed"
self.env = {
**os.environ, "PATH": f"{self.bin}:{os.environ['PATH']}",
"FIXTURES": str(self.root), "HANZO_INSTALL_REPO": "hanzoai/mcp",
"HANZO_INSTALL_BIN": "hanzo-mcp", "HANZO_INSTALL_ALIAS": "mcp",
"HANZO_INSTALL_PREFIX": str(self.prefix), "HANZO_VERSION": "",
"HANZO_INSTALL_TOKEN": "", "GH_TOKEN": "", "GITHUB_TOKEN": "",
}
self.executable("gh", "#!/bin/sh\nexit 1\n")
self.executable("uname", '#!/bin/sh\ncase "$1" in -s) echo Linux;; *) echo x86_64;; esac\n')
self.executable("curl", FAKE_CURL)
buffer = io.BytesIO()
with tarfile.open(fileobj=buffer, mode="w:gz") as archive:
contents = b"#!/bin/sh\necho native-mcp\n"
member = tarfile.TarInfo("hanzo-mcp")
member.size = len(contents)
member.mode = 0o755
archive.addfile(member, io.BytesIO(contents))
self.archive = buffer.getvalue()
self.checksum = f"{hashlib.sha256(self.archive).hexdigest()} {ARCHIVE}\n"
def executable(self, name, text):
path = self.bin / name
path.write_text(text)
path.chmod(0o755)
def tags(self, *tags):
"""Advertise refs the way github.com's smart HTTP does, peeled tags included."""
sha = "a" * 40
lines = [pkt("# service=git-upload-pack\n"), "0000",
pkt(f"{sha} HEAD\0multi_ack thin-pack side-band ofs-delta\n"),
pkt(f"{sha} refs/heads/main\n")]
for tag in tags:
lines += [pkt(f"{sha} refs/tags/{tag}\n"), pkt(f"{sha} refs/tags/{tag}^{{}}\n")]
(self.root / "refs").write_text("".join(lines) + "0000")
def release(self, tag, *targets, checksum=True):
directory = self.root / "releases" / tag
directory.mkdir(parents=True)
for target in targets:
name = f"hanzo-mcp-{target}.tar.gz"
(directory / name).write_bytes(self.archive)
if checksum:
(directory / f"{name}.sha256").write_text(self.checksum.replace(ARCHIVE, name))
def requests(self):
path = self.root / "requests"
return [json.loads(line) for line in path.read_text().splitlines()] if path.exists() else []
def install(self):
return subprocess.run(["sh", str(INSTALLER)], env=self.env,
capture_output=True, text=True, timeout=15)
def test_an_exhausted_api_limit_does_not_stop_an_install(self):
# 60 anonymous REST calls an hour per address, shared by everyone behind
# the same NAT: the anonymous install path makes none.
(self.root / "api-blocked").touch()
self.tags("v1.0.0")
self.release("v1.0.0", "linux-amd64")
result = self.install()
self.assertEqual(result.returncode, 0, result.stderr)
self.assertIn("v1.0.0 linux-amd64", result.stdout)
self.assertEqual([u for u, _ in self.requests() if "api.github.com" in u], [])
def test_semver_beats_tag_order_and_skips_other_products(self):
# MCP publishes JavaScript versions under v* in the same repository; they
# carry no native archive.
self.tags("rust-v1.9.9", "v8.0.0", "rust-v1.10.2", "rust-v2.0.0", "v9.0.0-beta.1")
for tag in ("rust-v1.9.9", "rust-v1.10.2"):
self.release(tag, "linux-amd64")
result = self.install()
self.assertEqual(result.returncode, 0, result.stderr)
self.assertIn("rust-v1.10.2 linux-amd64", result.stdout)
self.assertEqual(subprocess.check_output([self.prefix / "mcp"], text=True), "native-mcp\n")
def test_newest_release_without_this_platform_is_skipped(self):
# hanzoai/cli v8.5.158 shipped darwin and linux-arm64 only, and hanzoai/mcp
# rust-v1.1.23 linux only: the newest release is not the newest one a given
# machine can install, and a half-uploaded release has a tarball and no sum.
self.tags("v3.0.0", "v2.0.0", "v1.0.0")
self.release("v3.0.0", "darwin-arm64", "linux-arm64")
self.release("v2.0.0", "linux-amd64", checksum=False)
self.release("v1.0.0", "linux-amd64")
result = self.install()
self.assertEqual(result.returncode, 0, result.stderr)
self.assertIn("v1.0.0 linux-amd64", result.stdout)
def test_a_token_reads_a_private_repo_through_the_api(self):
# A private repo answers 404 to anonymous refs and downloads, so the
# token rides both: Basic on git's refs, Bearer on the API.
self.env["GH_TOKEN"] = "ghp_fixture"
self.tags("v2.0.0", "v1.0.0")
self.release("v2.0.0", "darwin-arm64")
self.release("v1.0.0", "linux-amd64")
result = self.install()
self.assertEqual(result.returncode, 0, result.stderr)
self.assertIn("v1.0.0 linux-amd64", result.stdout)
refs = [h for u, h in self.requests() if "/info/refs" in u]
self.assertEqual(refs, [["Authorization: Basic eC1hY2Nlc3MtdG9rZW46Z2hwX2ZpeHR1cmU="]])
self.assertEqual([u for u, _ in self.requests() if u.startswith("https://github.com/hanzoai/mcp/releases")], [])
def test_explicit_pin_skips_release_selection(self):
self.env["HANZO_VERSION"] = "rust-v1.0.0"
self.release("rust-v1.0.0", "linux-amd64")
result = self.install()
self.assertEqual(result.returncode, 0, result.stderr)
self.assertEqual([u for u, _ in self.requests() if "/info/refs" in u], [])
def test_refuses_checksum_mismatch_before_installing(self):
self.tags("v1.0.0")
self.release("v1.0.0", "linux-amd64")
(self.root / "releases" / "v1.0.0" / f"{ARCHIVE}.sha256").write_text(f"{'0' * 64} {ARCHIVE}\n")
result = self.install()
self.assertNotEqual(result.returncode, 0)
self.assertIn("checksum MISMATCH", result.stderr)
self.assertFalse(self.prefix.exists())
# A stand-in gh: signed in, and its `attestation verify` answers `verdict`
# and records how it was asked. One that is `old` predates the command, as
# Ubuntu's gh 2.45.0 does: `unknown command "attestation"`, exit 1.
def signed_in_gh(self, verdict, old=False):
attest = ('echo \'unknown command "attestation" for "gh"\' >&2; exit 1' if old else
f'[ "$3" = --help ] && {{ echo " --source-ref string"; exit 0; }}; '
f'echo "$@" >> "{self.root}/gh-asked"; echo "no attestation matched" >&2; exit {verdict}')
self.executable("gh", f"""#!/bin/sh
case "$1 $2" in
"auth status") exit 0 ;;
"auth token") exit 1 ;;
"--version ") echo "gh version 2.45.0 (2024-03-04)"; exit 0 ;;
"attestation verify") {attest} ;;
esac
exit 1
""")
def test_an_attested_tool_is_verified_against_its_workflow_and_tag(self):
self.env["HANZO_INSTALL_SIGNER"] = ".github/workflows/release.yml"
self.signed_in_gh(0)
self.tags("v1.0.0")
self.release("v1.0.0", "linux-amd64")
result = self.install()
self.assertEqual(result.returncode, 0, result.stderr)
asked = (self.root / "gh-asked").read_text().split()
self.assertIn("--signer-workflow", asked)
self.assertEqual(asked[asked.index("--signer-workflow") + 1], "hanzoai/mcp/.github/workflows/release.yml")
self.assertEqual(asked[asked.index("--source-ref") + 1], "refs/tags/v1.0.0")
self.assertIn("provenance: hanzoai/mcp/.github/workflows/release.yml built", result.stdout)
def test_an_attestation_that_does_not_match_refuses_the_install(self):
self.env["HANZO_INSTALL_SIGNER"] = ".github/workflows/release.yml"
self.signed_in_gh(1)
self.tags("v1.0.0")
self.release("v1.0.0", "linux-amd64")
result = self.install()
self.assertNotEqual(result.returncode, 0)
self.assertIn("refusing to install", result.stderr)
self.assertIn("no attestation matched", result.stderr, "gh's own reason is shown")
self.assertFalse((self.prefix / "hanzo-mcp").exists())
def test_a_gh_that_cannot_verify_says_so_and_does_not_refuse(self):
# gh before `attestation verify --source-ref` cannot check anything: that is
# an unchecked install, named with its version, never a tamper verdict.
self.env["HANZO_INSTALL_SIGNER"] = ".github/workflows/release.yml"
self.signed_in_gh(0, old=True)
self.tags("v1.0.0")
self.release("v1.0.0", "linux-amd64")
result = self.install()
self.assertEqual(result.returncode, 0, result.stderr)
self.assertIn("provenance not checked (gh version 2.45.0 (2024-03-04) cannot check an attestation's tag)", result.stdout)
self.assertIn("--source-ref refs/tags/v1.0.0", result.stdout)
self.assertFalse((self.root / "gh-asked").exists())
def test_an_attested_tool_installs_from_its_release_tags_only(self):
# Its release workflow runs on vX.Y.Z and its ruleset guards v*: a higher
# rust-v tag carrying the right asset names is no release of it.
self.env["HANZO_INSTALL_SIGNER"] = ".github/workflows/release.yml"
self.tags("rust-v9.9.9", "v1.0.0")
self.release("rust-v9.9.9", "linux-amd64")
self.release("v1.0.0", "linux-amd64")
result = self.install()
self.assertEqual(result.returncode, 0, result.stderr)
self.assertIn("v1.0.0 linux-amd64", result.stdout)
self.assertNotIn("rust-v9.9.9", result.stdout)
self.env["HANZO_VERSION"] = "rust-v9.9.9"
result = self.install()
self.assertNotEqual(result.returncode, 0)
self.assertIn("rust-v9.9.9 is not a release tag of hanzoai/mcp (vX.Y.Z)", result.stderr)
def test_without_a_signed_in_gh_the_install_says_provenance_was_not_checked(self):
self.env["HANZO_INSTALL_SIGNER"] = ".github/workflows/release.yml"
self.tags("v1.0.0")
self.release("v1.0.0", "linux-amd64")
result = self.install()
self.assertEqual(result.returncode, 0, result.stderr)
self.assertIn("provenance not checked (needs a signed-in gh)", result.stdout)
self.assertIn("gh attestation verify", result.stdout)
self.assertIn("--source-ref refs/tags/v1.0.0", result.stdout)
def test_no_stable_release_with_this_build_fails(self):
self.tags("v2.0.0", "v1.0.0-rc.1")
self.release("v1.0.0-rc.1", "linux-amd64")
result = self.install()
self.assertNotEqual(result.returncode, 0)
self.assertIn("no stable release of hanzoai/mcp has a build for linux-amd64", result.stderr)
def test_unreachable_refs_fail(self):
result = self.install()
self.assertNotEqual(result.returncode, 0)
self.assertIn("could not list the tags of hanzoai/mcp", result.stderr)
if __name__ == "__main__":
unittest.main()