Files
python-sdk/pkg/hanzo-cli/tests/test_s3.py
T
zeekay f6c516868c feat(cli,mcp): complete S3 control surface — hanzo s3 + s3 MCP tool
Closes the last gap in the one-way Hanzo control surface. IAM/KMS/PaaS
already had both CLI subcommands and MCP tools; S3 had neither.

- hanzo s3: buckets/mb/rb/ls/stat/rm/presign over hanzo_s3 (MinIO) client,
  creds from env (inject from KMS, never plaintext). Registered in the
  unified hanzo CLI alongside iam/kms/paas.
- hanzo-tools-s3: S3Tool MCP tool (same action-dispatch pattern as PaaSTool),
  exported via the hanzo.tools entry point; s3 added to loader prefix map.
- uv workspace: map hanzo-cli/iam/kms/s3 + tools-s3 to local sources so the
  unified CLI resolves on-disk packages (fixes hanzo_iam ImportError from a
  stale published 1.29.0 wheel shadowing local 1.30.0).

Tests: 7 CLI + 14 MCP, all green.
2026-06-24 13:50:24 -07:00

78 lines
2.3 KiB
Python

"""Unit tests for the `hanzo s3` CLI group (mocked S3 client)."""
from __future__ import annotations
from datetime import datetime
from unittest.mock import MagicMock, patch
from click.testing import CliRunner
from hanzo_cli.s3.commands import s3
def _patch_client(client):
return patch("hanzo_cli.s3.commands._get_s3_client", return_value=client)
def test_s3_group_help():
result = CliRunner().invoke(s3, ["--help"])
assert result.exit_code == 0
for cmd in ("buckets", "mb", "rb", "ls", "stat", "rm", "presign"):
assert cmd in result.output
def test_buckets_lists():
client = MagicMock()
b = MagicMock(creation_date=datetime(2026, 1, 1))
b.name = "hanzo-space"
client.list_buckets.return_value = [b]
with _patch_client(client):
result = CliRunner().invoke(s3, ["buckets"])
assert result.exit_code == 0
assert "hanzo-space" in result.output
def test_make_bucket():
client = MagicMock()
with _patch_client(client):
result = CliRunner().invoke(s3, ["mb", "b1"])
assert result.exit_code == 0
client.make_bucket.assert_called_once_with("b1")
def test_ls_objects():
client = MagicMock()
obj = MagicMock(object_name="a.txt", size=12, is_dir=False,
last_modified=datetime(2026, 1, 1))
client.list_objects.return_value = [obj]
with _patch_client(client):
result = CliRunner().invoke(s3, ["ls", "b1", "--prefix", "a"])
assert result.exit_code == 0
assert "a.txt" in result.output
def test_presign_outputs_url():
client = MagicMock()
client.presigned_get_object.return_value = "https://s3.hanzo.ai/b1/a.txt?sig=x"
with _patch_client(client):
result = CliRunner().invoke(s3, ["presign", "b1", "a.txt"])
assert result.exit_code == 0
assert "https://s3.hanzo.ai/" in result.output
def test_rm_confirms(monkeypatch):
client = MagicMock()
with _patch_client(client):
# Decline confirmation → aborts, client not called.
result = CliRunner().invoke(s3, ["rm", "b1", "a.txt"], input="n\n")
assert result.exit_code != 0
client.remove_object.assert_not_called()
def test_missing_credentials():
runner = CliRunner()
with patch.dict("os.environ", {}, clear=True):
result = runner.invoke(s3, ["buckets"])
assert result.exit_code != 0
assert "credentials" in result.output.lower()