mirror of
https://github.com/hanzoai/python-sdk.git
synced 2026-10-27 04:20:09 +00:00
255 lines
7.1 KiB
Python
255 lines
7.1 KiB
Python
"""Playground client and types test suite.
|
|
|
|
Covers type construction, serialization, PlaygroundClient instantiation,
|
|
and URL/header configuration.
|
|
"""
|
|
|
|
import json
|
|
from dataclasses import asdict
|
|
|
|
import pytest
|
|
import httpx
|
|
|
|
from hanzo_zap import (
|
|
PlaygroundClient,
|
|
AgentEvent,
|
|
AgentInfo,
|
|
CommitInfo,
|
|
EventMsg,
|
|
FileChange,
|
|
RealtimeAudioFrame,
|
|
Submission,
|
|
)
|
|
|
|
|
|
# -- Playground types --------------------------------------------------------
|
|
|
|
|
|
class TestSubmission:
|
|
def test_fields(self):
|
|
s = Submission(id="sub-1", op={"type": "inject", "message": "hi"})
|
|
assert s.id == "sub-1"
|
|
assert s.op["type"] == "inject"
|
|
assert s.trace is None
|
|
|
|
def test_with_trace(self):
|
|
s = Submission(
|
|
id="sub-2",
|
|
op={"type": "broadcast"},
|
|
trace={"request_id": "abc"},
|
|
)
|
|
assert s.trace["request_id"] == "abc"
|
|
|
|
def test_serializes(self):
|
|
s = Submission(id="sub-3", op={"type": "noop"})
|
|
d = asdict(s)
|
|
assert d == {"id": "sub-3", "op": {"type": "noop"}, "trace": None}
|
|
|
|
|
|
class TestEventMsg:
|
|
def test_defaults(self):
|
|
e = EventMsg(type="turn_started")
|
|
assert e.type == "turn_started"
|
|
assert e.data == {}
|
|
assert e.raw is None
|
|
|
|
def test_with_data(self):
|
|
e = EventMsg(type="agent_message", data={"text": "hello"})
|
|
assert e.data["text"] == "hello"
|
|
|
|
def test_with_raw(self):
|
|
e = EventMsg(type="binary", raw=b"\x00\x01")
|
|
assert e.raw == b"\x00\x01"
|
|
|
|
def test_serializes(self):
|
|
e = EventMsg(type="turn_completed", data={"tokens": 42})
|
|
d = asdict(e)
|
|
assert d["type"] == "turn_completed"
|
|
assert d["data"]["tokens"] == 42
|
|
|
|
|
|
class TestAgentEvent:
|
|
def test_required_fields(self):
|
|
ae = AgentEvent(type="message", space_id="sp-1", agent_id="ag-1")
|
|
assert ae.type == "message"
|
|
assert ae.space_id == "sp-1"
|
|
assert ae.agent_id == "ag-1"
|
|
assert ae.agent_name == ""
|
|
assert ae.timestamp == ""
|
|
assert ae.data == {}
|
|
|
|
def test_full(self):
|
|
ae = AgentEvent(
|
|
type="tool_call",
|
|
space_id="sp-1",
|
|
agent_id="ag-2",
|
|
agent_name="coder",
|
|
timestamp="2026-03-19T00:00:00Z",
|
|
data={"tool": "read_file", "path": "/tmp/x"},
|
|
)
|
|
assert ae.agent_name == "coder"
|
|
assert ae.data["tool"] == "read_file"
|
|
|
|
|
|
class TestAgentInfo:
|
|
def test_defaults(self):
|
|
ai = AgentInfo(agent_id="ag-1")
|
|
assert ai.agent_id == "ag-1"
|
|
assert ai.did == ""
|
|
assert ai.status == "offline"
|
|
assert ai.capabilities == []
|
|
assert ai.model == ""
|
|
|
|
def test_full(self):
|
|
ai = AgentInfo(
|
|
agent_id="ag-2",
|
|
did="did:key:abc",
|
|
space_id="sp-1",
|
|
display_name="Coder",
|
|
status="online",
|
|
capabilities=[{"name": "code"}],
|
|
model="zen-405b",
|
|
)
|
|
assert ai.display_name == "Coder"
|
|
assert ai.status == "online"
|
|
assert len(ai.capabilities) == 1
|
|
|
|
def test_serializes(self):
|
|
ai = AgentInfo(agent_id="ag-3", model="zen-32b")
|
|
d = asdict(ai)
|
|
assert d["agent_id"] == "ag-3"
|
|
assert d["model"] == "zen-32b"
|
|
|
|
|
|
class TestRealtimeAudioFrame:
|
|
def test_defaults(self):
|
|
f = RealtimeAudioFrame(data="AAAA")
|
|
assert f.data == "AAAA"
|
|
assert f.sample_rate == 16000
|
|
assert f.num_channels == 1
|
|
|
|
def test_custom(self):
|
|
f = RealtimeAudioFrame(data="base64data", sample_rate=44100, num_channels=2)
|
|
assert f.sample_rate == 44100
|
|
assert f.num_channels == 2
|
|
|
|
|
|
class TestFileChange:
|
|
def test_fields(self):
|
|
fc = FileChange(path="src/main.py", status="modified")
|
|
assert fc.path == "src/main.py"
|
|
assert fc.status == "modified"
|
|
|
|
|
|
class TestCommitInfo:
|
|
def test_fields(self):
|
|
ci = CommitInfo(
|
|
hash="abc123",
|
|
message="fix: resolve issue",
|
|
author="Test",
|
|
email="[email protected]",
|
|
timestamp="2026-03-19T00:00:00Z",
|
|
)
|
|
assert ci.hash == "abc123"
|
|
assert ci.message == "fix: resolve issue"
|
|
assert ci.author == "Test"
|
|
assert ci.email == "[email protected]"
|
|
|
|
def test_serializes(self):
|
|
ci = CommitInfo(
|
|
hash="def456",
|
|
message="feat: add playground",
|
|
author="Dev",
|
|
email="[email protected]",
|
|
timestamp="2026-03-19T12:00:00Z",
|
|
)
|
|
d = asdict(ci)
|
|
assert d["hash"] == "def456"
|
|
assert d["author"] == "Dev"
|
|
|
|
|
|
# -- PlaygroundClient --------------------------------------------------------
|
|
|
|
|
|
class TestPlaygroundClient:
|
|
def test_default_url(self):
|
|
pc = PlaygroundClient()
|
|
assert pc.base_url == "http://localhost:8080"
|
|
|
|
def test_custom_url(self):
|
|
pc = PlaygroundClient(base_url="https://playground.hanzo.ai/")
|
|
assert pc.base_url == "https://playground.hanzo.ai"
|
|
|
|
def test_trailing_slash_stripped(self):
|
|
pc = PlaygroundClient(base_url="http://localhost:9090/")
|
|
assert pc.base_url == "http://localhost:9090"
|
|
|
|
def test_no_auth_header_without_token(self):
|
|
pc = PlaygroundClient()
|
|
assert "Authorization" not in pc._client.headers
|
|
|
|
def test_auth_header_with_token(self):
|
|
pc = PlaygroundClient(token="test-token-123")
|
|
assert pc._client.headers["Authorization"] == "Bearer test-token-123"
|
|
|
|
def test_timeout(self):
|
|
pc = PlaygroundClient()
|
|
assert pc._client.timeout.connect == 30.0
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_context_manager(self):
|
|
async with PlaygroundClient() as pc:
|
|
assert pc.base_url == "http://localhost:8080"
|
|
# client should be closed after exit
|
|
assert pc._client.is_closed
|
|
|
|
|
|
# -- Import sanity -----------------------------------------------------------
|
|
|
|
|
|
class TestImports:
|
|
"""Verify all new symbols are importable from hanzo_zap top-level."""
|
|
|
|
def test_playground_client(self):
|
|
from hanzo_zap import PlaygroundClient
|
|
assert PlaygroundClient is not None
|
|
|
|
def test_all_types(self):
|
|
from hanzo_zap import (
|
|
Submission,
|
|
EventMsg,
|
|
AgentEvent,
|
|
AgentInfo,
|
|
RealtimeAudioFrame,
|
|
FileChange,
|
|
CommitInfo,
|
|
)
|
|
# All should be callable dataclasses
|
|
assert callable(Submission)
|
|
assert callable(EventMsg)
|
|
assert callable(AgentEvent)
|
|
assert callable(AgentInfo)
|
|
assert callable(RealtimeAudioFrame)
|
|
assert callable(FileChange)
|
|
assert callable(CommitInfo)
|
|
|
|
def test_existing_exports_still_work(self):
|
|
from hanzo_zap import (
|
|
ZapClient,
|
|
ZapServer,
|
|
ApprovalPolicy,
|
|
SandboxPolicy,
|
|
MessageType,
|
|
Tool,
|
|
ToolCall,
|
|
ToolResult,
|
|
ServerInfo,
|
|
ClientInfo,
|
|
)
|
|
assert ZapClient is not None
|
|
assert ZapServer is not None
|
|
|
|
def test_version_bumped(self):
|
|
import hanzo_zap
|
|
assert hanzo_zap.__version__ == "0.7.0"
|