Files
hanzoai_agent/sdk/python/tests/test_client_execution_paths.py
zeekayandHanzo Dev 41b460b760 The Python client calls /v1/, which is what its tests assert
Every path in this suite registers /v1/... and none registers /api/v1/,
while the client built its base as {url}/api/v1 — so twelve client tests
failed the moment collection worked again. The extra segment is not a
shape this estate serves and nothing asserted it.

The UI subtree was built by replacing the first "/v1" found, under a
comment claiming it replaced the last. Appended instead, so a base url
carrying its own version segment cannot be rewritten. The test stub kept
a second copy of the client's base and had drifted from it.

Co-authored-by: Hanzo Dev <[email protected]>
2026-09-11 04:10:08 -07:00

436 lines
12 KiB
Python

"""
Comprehensive tests for HanzoAgentsClient execution paths.
"""
import asyncio
from unittest.mock import MagicMock
import pytest
import responses as responses_lib
from hanzo_agents.client import HanzoAgentsClient
from hanzo_agents.execution_context import ExecutionContext
@pytest.fixture
def client():
"""Create a test client."""
return HanzoAgentsClient(base_url="http://localhost:8080")
def test_call_sync_execution(client):
"""Test synchronous execution call."""
# Mock the async execution endpoint
responses_lib.add(
responses_lib.POST,
"http://localhost:8080/v1/execute/async/agent.reasoner",
json={"execution_id": "exec-123"},
status=200,
)
# Mock the status polling endpoint
responses_lib.add(
responses_lib.GET,
"http://localhost:8080/v1/executions/exec-123",
json={"status": "succeeded", "result": {"key": "value"}},
status=200,
)
response = client.execute_sync(
target="agent.reasoner",
input_data={"key": "value"},
)
assert response is not None
def test_call_async_execution(client):
"""Test asynchronous execution call."""
# Mock the async execution endpoint
responses_lib.add(
responses_lib.POST,
"http://localhost:8080/v1/execute/async/agent.reasoner",
json={"execution_id": "exec-456"},
status=200,
)
# Mock the status polling endpoint
responses_lib.add(
responses_lib.GET,
"http://localhost:8080/v1/executions/exec-456",
json={"status": "succeeded", "result": {"key": "value"}},
status=200,
)
response = client.execute_sync(
target="agent.reasoner",
input_data={"key": "value"},
)
assert response is not None
assert "status" in response or "result" in response
def test_call_with_context_headers(client):
"""Test call with execution context headers."""
# Mock the async execution endpoint
responses_lib.add(
responses_lib.POST,
"http://localhost:8080/v1/execute/async/agent.reasoner",
json={"execution_id": "exec-789"},
status=200,
)
# Mock the status polling endpoint
responses_lib.add(
responses_lib.GET,
"http://localhost:8080/v1/executions/exec-789",
json={"status": "succeeded", "result": {"key": "value"}},
status=200,
)
mock_agent = MagicMock()
context = ExecutionContext(
execution_id="exec-1",
run_id="run-1",
agent_instance=mock_agent,
reasoner_name="reasoner-1",
parent_execution_id="parent-1",
)
# Set the context on the client
client._current_workflow_context = context
response = client.execute_sync(
target="agent.reasoner",
input_data={"key": "value"},
)
assert response is not None
def test_call_error_handling(client):
"""Test error handling in call method."""
# Mock error response from the async execution endpoint
responses_lib.add(
responses_lib.POST,
"http://localhost:8080/v1/execute/async/agent.reasoner",
json={"error": "Network error"},
status=500,
)
with pytest.raises(Exception):
client.execute_sync(target="agent.reasoner", input_data={"key": "value"})
def test_call_retry_logic(client):
"""Test retry logic for failed requests."""
# First call fails with 500
responses_lib.add(
responses_lib.POST,
"http://localhost:8080/v1/execute/async/agent.reasoner",
json={"error": "Transient error"},
status=500,
)
# Second call succeeds
responses_lib.add(
responses_lib.POST,
"http://localhost:8080/v1/execute/async/agent.reasoner",
json={"execution_id": "exec-retry"},
status=200,
)
# Mock the status polling endpoint
responses_lib.add(
responses_lib.GET,
"http://localhost:8080/v1/executions/exec-retry",
json={"status": "succeeded", "result": {"key": "value"}},
status=200,
)
# Should retry and eventually succeed
# Note: This test may fail if the client doesn't implement retry logic
# In that case, it will raise an exception on the first 500 response
try:
response = client.execute_sync(
target="agent.reasoner", input_data={"key": "value"}
)
assert response is not None
except Exception:
# If client doesn't retry, this is expected behavior
pass
def test_call_with_webhook_config(client):
"""Test call with webhook configuration."""
# Mock the async execution endpoint
responses_lib.add(
responses_lib.POST,
"http://localhost:8080/v1/execute/async/agent.reasoner",
json={"execution_id": "exec-webhook"},
status=200,
)
# Mock the status polling endpoint
responses_lib.add(
responses_lib.GET,
"http://localhost:8080/v1/executions/exec-webhook",
json={"status": "succeeded", "result": {"key": "value"}},
status=200,
)
webhook_config = {
"url": "https://example.com/webhook",
"secret": "test-secret",
}
# Note: execute_sync doesn't accept webhook parameter directly
# Webhook config would be passed via input_data
response = client.execute_sync(
target="agent.reasoner",
input_data={"key": "value", "webhook": webhook_config},
)
assert response is not None
def test_call_header_propagation(client):
"""Test header propagation in call method."""
# Mock the async execution endpoint
responses_lib.add(
responses_lib.POST,
"http://localhost:8080/v1/execute/async/agent.reasoner",
json={"execution_id": "exec-header"},
status=200,
)
# Mock the status polling endpoint
responses_lib.add(
responses_lib.GET,
"http://localhost:8080/v1/executions/exec-header",
json={"status": "succeeded", "result": {"key": "value"}},
status=200,
)
# Set current workflow context
mock_agent = MagicMock()
context = ExecutionContext(
execution_id="exec-1",
run_id="run-1",
agent_instance=mock_agent,
reasoner_name="reasoner-1",
)
client._current_workflow_context = context
response = client.execute_sync(
target="agent.reasoner",
input_data={"key": "value"},
)
assert response is not None
def test_call_event_stream_handling(client):
"""Test event stream handling in call method."""
# Mock the async execution endpoint
responses_lib.add(
responses_lib.POST,
"http://localhost:8080/v1/execute/async/agent.reasoner",
json={"execution_id": "exec-stream"},
status=200,
)
# Mock the status polling endpoint with streaming response
responses_lib.add(
responses_lib.GET,
"http://localhost:8080/v1/executions/exec-stream",
json={"status": "succeeded", "result": {"key": "value"}},
status=200,
)
response = client.execute_sync(
target="agent.reasoner",
input_data={"key": "value"},
)
assert response is not None
def test_call_timeout_handling(client):
"""Test timeout handling in call method."""
# Mock timeout error using responses
responses_lib.add(
responses_lib.POST,
"http://localhost:8080/v1/execute/async/agent.reasoner",
body=asyncio.TimeoutError(),
)
with pytest.raises((asyncio.TimeoutError, Exception)):
client.execute_sync(
target="agent.reasoner",
input_data={"key": "value"},
)
def test_call_with_different_execution_modes(client):
"""Test call with different execution modes."""
# Mock first call
responses_lib.add(
responses_lib.POST,
"http://localhost:8080/v1/execute/async/agent.reasoner",
json={"execution_id": "exec-mode1"},
status=200,
)
responses_lib.add(
responses_lib.GET,
"http://localhost:8080/v1/executions/exec-mode1",
json={"status": "succeeded", "result": {"key": "value"}},
status=200,
)
# Mock second call
responses_lib.add(
responses_lib.POST,
"http://localhost:8080/v1/execute/async/agent.reasoner",
json={"execution_id": "exec-mode2"},
status=200,
)
responses_lib.add(
responses_lib.GET,
"http://localhost:8080/v1/executions/exec-mode2",
json={"status": "succeeded", "result": {"key": "value"}},
status=200,
)
# Test sync mode
response_sync = client.execute_sync(
target="agent.reasoner",
input_data={"key": "value"},
)
assert response_sync is not None
# Test async mode (both use execute_sync in current implementation)
response_async = client.execute_sync(
target="agent.reasoner",
input_data={"key": "value"},
)
assert response_async is not None
def test_call_result_caching(client):
"""Test result caching in call method."""
# Mock first call
responses_lib.add(
responses_lib.POST,
"http://localhost:8080/v1/execute/async/agent.reasoner",
json={"execution_id": "exec-cache1"},
status=200,
)
responses_lib.add(
responses_lib.GET,
"http://localhost:8080/v1/executions/exec-cache1",
json={"status": "succeeded", "result": {"key": "value"}},
status=200,
)
# Mock second call
responses_lib.add(
responses_lib.POST,
"http://localhost:8080/v1/execute/async/agent.reasoner",
json={"execution_id": "exec-cache2"},
status=200,
)
responses_lib.add(
responses_lib.GET,
"http://localhost:8080/v1/executions/exec-cache2",
json={"status": "succeeded", "result": {"key": "value"}},
status=200,
)
# First call
response1 = client.execute_sync(
target="agent.reasoner",
input_data={"key": "value"},
)
# Second call with same input (should use cache if enabled)
response2 = client.execute_sync(
target="agent.reasoner",
input_data={"key": "value"},
)
assert response1 is not None
assert response2 is not None
def test_call_with_custom_headers(client):
"""Test call with custom headers."""
# Mock the async execution endpoint
responses_lib.add(
responses_lib.POST,
"http://localhost:8080/v1/execute/async/agent.reasoner",
json={"execution_id": "exec-custom"},
status=200,
)
# Mock the status polling endpoint
responses_lib.add(
responses_lib.GET,
"http://localhost:8080/v1/executions/exec-custom",
json={"status": "succeeded", "result": {"key": "value"}},
status=200,
)
custom_headers = {"X-Custom-Header": "custom-value"}
response = client.execute_sync(
target="agent.reasoner",
input_data={"key": "value"},
headers=custom_headers,
)
assert response is not None
def test_call_context_management(client):
"""Test context management in call method."""
# Mock the async execution endpoint
responses_lib.add(
responses_lib.POST,
"http://localhost:8080/v1/execute/async/agent.reasoner",
json={"execution_id": "exec-context"},
status=200,
)
# Mock the status polling endpoint
responses_lib.add(
responses_lib.GET,
"http://localhost:8080/v1/executions/exec-context",
json={"status": "succeeded", "result": {"key": "value"}},
status=200,
)
# Test that context is properly managed
mock_agent = MagicMock()
context = ExecutionContext(
execution_id="exec-1",
run_id="run-1",
agent_instance=mock_agent,
reasoner_name="reasoner-1",
)
# Set context
client._current_workflow_context = context
response = client.execute_sync(
target="agent.reasoner",
input_data={"key": "value"},
)
assert response is not None
# Context should still be set
assert client._current_workflow_context == context
def test_call_error_response_handling(client):
"""Test handling of error responses."""
# Mock error response from the async execution endpoint
responses_lib.add(
responses_lib.POST,
"http://localhost:8080/v1/execute/async/agent.reasoner",
json={"error": "Internal server error"},
status=500,
)
with pytest.raises(Exception):
client.execute_sync(target="agent.reasoner", input_data={"key": "value"})