Files
python-sdk/pkg/hanzo-cli/tests/test_e2e.py
T
zeekayandHanzo Dev 0f9890725e test(cli): assert the package version, not a hardcoded literal
test_version read `assert "0.1.0" in r.stdout` and passed only because
hanzo_cli.__version__ was stale at 0.1.0 while pyproject said 0.2.2. The one
test positioned to catch the version drift was asserting the drifted value, so
single-sourcing the version in f453dae8 is what finally made it fail. It now
compares against hanzo_cli.__version__ and cannot enshrine a wrong value again.

Co-authored-by: Hanzo Dev <[email protected]>
2026-07-26 14:58:15 -07:00

308 lines
9.7 KiB
Python

"""End-to-end tests for hanzo CLI against live services.
Requires:
- hanzo-cli installed (pip install -e pkg/hanzo-cli)
- Valid credentials stored at ~/.hanzo/auth/token.json (run hanzo login first)
- Live services: hanzo.id, platform.hanzo.ai
Run:
pytest pkg/hanzo-cli/tests/test_e2e.py -v -s
"""
from __future__ import annotations
import subprocess
import sys
import time
import pytest
# Invoke THIS package's command tree, not whatever `hanzo` resolves to on PATH.
# hanzo_cli no longer installs a `hanzo` script (that name belongs to the
# `hanzo` package), so a bare "hanzo" here would silently exercise a different
# CLI than the one these tests are written against.
HANZO_CMD = [sys.executable, "-m", "hanzo_cli"]
TIMEOUT = 30
def run(args: list[str], check: bool = True) -> subprocess.CompletedProcess:
"""Run a hanzo CLI command and return the result."""
result = subprocess.run(
HANZO_CMD + args,
capture_output=True,
text=True,
timeout=TIMEOUT,
)
if check and result.returncode != 0:
pytest.fail(
f"hanzo {' '.join(args)} failed (rc={result.returncode}):\n"
f"stdout: {result.stdout}\n"
f"stderr: {result.stderr}"
)
return result
# =========================================================================
# Auth
# =========================================================================
class TestAuth:
def test_version(self):
# Assert against the package's own version, never a literal. This line
# used to read `assert "0.1.0" in r.stdout` and passed only because
# hanzo_cli.__version__ was stale at 0.1.0 while pyproject said 0.2.2 —
# the test was enshrining the drift it should have caught.
from hanzo_cli import __version__
r = run(["--version"])
assert __version__ in r.stdout
def test_help(self):
r = run(["--help"])
assert "login" in r.stdout
assert "paas" in r.stdout
assert "iam" in r.stdout
assert "kms" in r.stdout
def test_whoami(self):
r = run(["whoami"])
assert "[email protected]" in r.stdout or "z" in r.stdout
assert "hanzo.id" in r.stdout
def test_whoami_shows_org(self):
r = run(["whoami"])
assert "hanzo" in r.stdout
# =========================================================================
# IAM
# =========================================================================
class TestIAM:
def test_iam_help(self):
r = run(["iam", "--help"])
assert "users" in r.stdout
assert "set-password" in r.stdout
def test_iam_users(self):
"""List users — should return at least user z."""
r = run(["iam", "users"], check=False)
# May fail if bearer token doesn't have admin access,
# but the command should at least not crash
assert r.returncode == 0 or "Not authenticated" not in r.stderr
def test_iam_orgs(self):
r = run(["iam", "orgs"], check=False)
assert r.returncode == 0 or "Not authenticated" not in r.stderr
# =========================================================================
# PaaS — Read Operations
# =========================================================================
class TestPaaSRead:
def test_paas_help(self):
r = run(["paas", "--help"])
assert "deploy" in r.stdout
assert "orgs" in r.stdout
assert "use" in r.stdout
def test_paas_orgs(self):
r = run(["paas", "orgs"])
assert "Hanzo" in r.stdout
assert "Organizations" in r.stdout
def test_paas_projects(self):
r = run(["paas", "projects", "--org", "698cda6739f65183b3009313"])
assert "Platform" in r.stdout
def test_paas_envs(self):
r = run(
[
"paas",
"envs",
"--org",
"698cda6739f65183b3009313",
"--project",
"698cda6739f65183b3009318",
]
)
assert "production" in r.stdout
def test_paas_context_set_and_show(self):
run(
[
"paas",
"use",
"--org",
"698cda6739f65183b3009313",
"--project",
"698cda6739f65183b3009318",
"--env",
"698cda6739f65183b300931c",
]
)
r = run(["paas", "context"])
assert "698cda6739f65183b3009313" in r.stdout
assert "698cda6739f65183b3009318" in r.stdout
assert "698cda6739f65183b300931c" in r.stdout
def test_paas_deploy_list(self):
"""List containers — may be empty, but should not error."""
r = run(["paas", "deploy", "list"])
assert r.returncode == 0
# =========================================================================
# PaaS — Deploy Lifecycle (create → status → logs → redeploy → delete)
# =========================================================================
class TestPaaSDeployLifecycle:
"""Full container lifecycle test using a lightweight Docker image."""
CONTAINER_NAME = "e2e-test-nginx"
@classmethod
def setup_class(cls):
"""Ensure context is set and clean up any leftover test containers."""
run(
[
"paas",
"use",
"--org",
"698cda6739f65183b3009313",
"--project",
"698cda6739f65183b3009318",
"--env",
"698cda6739f65183b300931c",
]
)
# Clean up leftover from previous failed runs
_r = run(["paas", "deploy", "delete", cls.CONTAINER_NAME, "-y"], check=False)
@classmethod
def teardown_class(cls):
"""Clean up test container."""
run(["paas", "deploy", "delete", cls.CONTAINER_NAME, "-y"], check=False)
def test_01_create(self):
r = run(
[
"paas",
"deploy",
"create",
self.CONTAINER_NAME,
"--image",
"nginx:latest",
"--port",
"80",
]
)
assert "Created container" in r.stdout
assert self.CONTAINER_NAME in r.stdout
def test_02_list_contains_container(self):
r = run(["paas", "deploy", "list"])
assert self.CONTAINER_NAME in r.stdout
def test_03_status_shows_pod(self):
"""Wait for the pod to appear and check status."""
for _attempt in range(6):
r = run(["paas", "deploy", "status", self.CONTAINER_NAME])
if "Running" in r.stdout or "Pending" in r.stdout:
break
time.sleep(5)
assert self.CONTAINER_NAME in r.stdout
assert "deployment" in r.stdout
def test_04_wait_for_running(self):
"""Wait up to 60s for the pod to be Running."""
for _attempt in range(12):
r = run(["paas", "deploy", "status", self.CONTAINER_NAME])
if "Running" in r.stdout:
return
time.sleep(5)
pytest.fail(f"Pod not Running after 60s:\n{r.stdout}")
def test_05_logs(self):
"""Container logs should contain nginx startup messages."""
r = run(["paas", "deploy", "logs", self.CONTAINER_NAME])
assert "nginx" in r.stdout.lower() or "worker process" in r.stdout
def test_06_env_show(self):
"""Show env vars — should work even if empty."""
r = run(["paas", "deploy", "env", self.CONTAINER_NAME])
assert r.returncode == 0
def test_07_env_set(self):
"""Set an env var on the container."""
r = run(
[
"paas",
"deploy",
"env",
self.CONTAINER_NAME,
"TEST_VAR=hello_e2e",
]
)
assert "Set 1 variable" in r.stdout
def test_08_env_verify(self):
"""Verify the env var was set."""
r = run(["paas", "deploy", "env", self.CONTAINER_NAME])
assert "TEST_VAR" in r.stdout
def test_09_redeploy(self):
"""Trigger a redeploy."""
r = run(["paas", "deploy", "redeploy", self.CONTAINER_NAME])
assert "Redeployment triggered" in r.stdout
def test_10_delete(self):
"""Delete the container."""
r = run(["paas", "deploy", "delete", self.CONTAINER_NAME, "-y"])
assert "Deleted" in r.stdout
def test_11_verify_deleted(self):
"""Verify the container no longer exists."""
r = run(["paas", "deploy", "list"])
assert self.CONTAINER_NAME not in r.stdout
# =========================================================================
# PaaS — Error Handling
# =========================================================================
class TestPaaSErrors:
def test_missing_context(self):
"""Commands without required context should fail gracefully."""
run(["paas", "use", "--clear"])
r = run(["paas", "deploy", "list"], check=False)
assert r.returncode != 0
assert "required" in r.stdout.lower() or "required" in r.stderr.lower()
def test_nonexistent_container(self):
run(
[
"paas",
"use",
"--org",
"698cda6739f65183b3009313",
"--project",
"698cda6739f65183b3009318",
"--env",
"698cda6739f65183b300931c",
]
)
r = run(["paas", "deploy", "status", "nonexistent-container-xyz"], check=False)
assert r.returncode != 0
assert "not found" in r.stdout.lower() or "not found" in r.stderr.lower()
def test_create_missing_image_and_repo(self):
r = run(["paas", "deploy", "create", "bad-deploy"], check=False)
assert r.returncode != 0