mirror of
https://github.com/hanzoai/python-sdk.git
synced 2026-10-27 04:20:09 +00:00
_users() called /v1/iam/get-users and then did
users = data if isinstance(data, list) else []
The verb answered a bare JSON list. The native /v1/iam/users answers
{"users": [...]}. So swapping only the path would have left that isinstance check
False and reported ZERO users for a healthy org — an empty result, not an error,
surfaced through an MCP tool an agent then reasons over. Both halves change here.
The dict branch also accepts "data" so a server still on the legacy envelope
keeps working during rollout; it goes when the compat surface does.
Co-authored-by: Hanzo Dev <[email protected]>
63 lines
2.0 KiB
Python
63 lines
2.0 KiB
Python
"""Tests for the IAM tool's canonical /v1/iam/ surface (HIP-0111/HIP-0026).
|
|
|
|
The Hanzo IAM server serves admin CRUD only under /v1/iam/; the upstream-shape
|
|
/api/ path falls through to a 200 text/html SPA catch-all. These tests pin the
|
|
URL builder to /v1/iam/ and prove the health check rejects that HTML catch-all.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
|
|
import httpx
|
|
|
|
from hanzo_tools.iam.iam_tool import IAMTool, _iam_url, IAM_BASE_URL
|
|
|
|
|
|
def test_iam_url_uses_v1_iam_not_api():
|
|
url = _iam_url("users")
|
|
assert url == f"{IAM_BASE_URL}/v1/iam/users"
|
|
assert "/api/" not in url
|
|
|
|
|
|
def test_iam_url_strips_leading_slash():
|
|
assert _iam_url("/enforce") == f"{IAM_BASE_URL}/v1/iam/enforce"
|
|
|
|
|
|
def _patched_client(monkeypatch, handler):
|
|
real_init = httpx.AsyncClient.__init__
|
|
|
|
def init(self, *args, **kwargs):
|
|
kwargs["transport"] = httpx.MockTransport(handler)
|
|
real_init(self, *args, **kwargs)
|
|
|
|
monkeypatch.setattr(httpx.AsyncClient, "__init__", init)
|
|
|
|
|
|
async def test_health_hits_canonical_healthz(monkeypatch):
|
|
seen = {}
|
|
|
|
def handler(request: httpx.Request) -> httpx.Response:
|
|
seen["url"] = str(request.url)
|
|
return httpx.Response(
|
|
200,
|
|
json={"status": "ok", "data": {"service": "iam", "version": "v1.18.3"}},
|
|
)
|
|
|
|
_patched_client(monkeypatch, handler)
|
|
out = json.loads(await IAMTool()._health())
|
|
assert seen["url"] == f"{IAM_BASE_URL}/v1/iam/healthz"
|
|
assert out["status"] == "ok"
|
|
assert out["data"]["service"] == "iam"
|
|
|
|
|
|
async def test_health_rejects_html_spa_catch_all(monkeypatch):
|
|
# A 200 text/html SPA response must NOT be reported as healthy.
|
|
def handler(request: httpx.Request) -> httpx.Response:
|
|
return httpx.Response(200, text="<!DOCTYPE html><html></html>",
|
|
headers={"content-type": "text/html; charset=utf-8"})
|
|
|
|
_patched_client(monkeypatch, handler)
|
|
out = json.loads(await IAMTool()._health())
|
|
assert out["status"] == "error"
|