149 lines
6.1 KiB
Python
149 lines
6.1 KiB
Python
"""Tests for the transport ABC, registry, and AnthropicTransport."""
|
|
|
|
import pytest
|
|
from types import SimpleNamespace
|
|
from unittest.mock import MagicMock
|
|
|
|
from agent.transports.base import ProviderTransport
|
|
from agent.transports.types import NormalizedResponse, ToolCall, Usage
|
|
from agent.transports import get_transport, register_transport, _REGISTRY
|
|
|
|
|
|
# ── ABC contract tests ──────────────────────────────────────────────────
|
|
|
|
class TestProviderTransportABC:
|
|
"""Verify the ABC contract is enforceable."""
|
|
|
|
def test_cannot_instantiate_abc(self):
|
|
with pytest.raises(TypeError):
|
|
ProviderTransport()
|
|
|
|
def test_concrete_must_implement_all_abstract(self):
|
|
class Incomplete(ProviderTransport):
|
|
@property
|
|
def api_mode(self):
|
|
return "test"
|
|
with pytest.raises(TypeError):
|
|
Incomplete()
|
|
|
|
def test_minimal_concrete(self):
|
|
class Minimal(ProviderTransport):
|
|
@property
|
|
def api_mode(self):
|
|
return "test_minimal"
|
|
def convert_messages(self, messages, **kw):
|
|
return messages
|
|
def convert_tools(self, tools):
|
|
return tools
|
|
def build_kwargs(self, model, messages, tools=None, **params):
|
|
return {"model": model, "messages": messages}
|
|
def normalize_response(self, response, **kw):
|
|
return NormalizedResponse(content="ok", tool_calls=None, finish_reason="stop")
|
|
|
|
t = Minimal()
|
|
assert t.api_mode == "test_minimal"
|
|
assert t.validate_response(None) is True # default
|
|
assert t.extract_cache_stats(None) is None # default
|
|
assert t.map_finish_reason("end_turn") == "end_turn" # default passthrough
|
|
|
|
|
|
# ── Registry tests ───────────────────────────────────────────────────────
|
|
|
|
class TestTransportRegistry:
|
|
|
|
def test_get_unregistered_returns_none(self):
|
|
assert get_transport("nonexistent_mode") is None
|
|
|
|
def test_anthropic_registered_via_plugin(self):
|
|
"""Anthropic transport is registered by the anthropic plugin, not a core module."""
|
|
# Register a mock transport via the registry API — core tests must
|
|
# not import hermes_agent_* packages.
|
|
from agent.plugin_registries import registries
|
|
from agent.transports.base import ProviderTransport
|
|
from agent.transports import register_transport
|
|
|
|
class _MockAnthropicTransport(ProviderTransport):
|
|
@property
|
|
def api_mode(self):
|
|
return "anthropic_messages"
|
|
def convert_messages(self, messages, **kw):
|
|
return messages
|
|
def convert_tools(self, tools, **kw):
|
|
return tools
|
|
def convert_response(self, resp, **kw):
|
|
return resp
|
|
def build_kwargs(self, model, messages, tools=None, **params):
|
|
return {}
|
|
def normalize_response(self, response, **kw):
|
|
return NormalizedResponse(content=None, tool_calls=None, finish_reason="stop")
|
|
|
|
register_transport("anthropic_messages", _MockAnthropicTransport)
|
|
t = get_transport("anthropic_messages")
|
|
assert t is not None
|
|
assert t.api_mode == "anthropic_messages"
|
|
|
|
def test_discovers_missing_transport_when_registry_partially_populated(self):
|
|
"""Importing one transport directly must not hide other valid api_modes."""
|
|
import agent.transports.chat_completions # noqa: F401
|
|
t = get_transport("codex_responses")
|
|
assert t is not None
|
|
assert t.api_mode == "codex_responses"
|
|
|
|
def test_register_and_get(self):
|
|
class DummyTransport(ProviderTransport):
|
|
@property
|
|
def api_mode(self):
|
|
return "dummy_test"
|
|
def convert_messages(self, messages, **kw):
|
|
return messages
|
|
def convert_tools(self, tools):
|
|
return tools
|
|
def build_kwargs(self, model, messages, tools=None, **params):
|
|
return {}
|
|
def normalize_response(self, response, **kw):
|
|
return NormalizedResponse(content=None, tool_calls=None, finish_reason="stop")
|
|
|
|
register_transport("dummy_test", DummyTransport)
|
|
t = get_transport("dummy_test")
|
|
assert t.api_mode == "dummy_test"
|
|
# Cleanup
|
|
_REGISTRY.pop("dummy_test", None)
|
|
|
|
|
|
# ── AnthropicTransport tests ────────────────────────────────────────────
|
|
|
|
class TestAnthropicTransport:
|
|
"""Core transport registry checks for the anthropic transport slot.
|
|
|
|
Full behavioral tests (convert_tools, validate_response, normalize_response,
|
|
build_kwargs, etc.) live in plugins/model-providers/anthropic/tests/ where
|
|
the real transport implementation is available.
|
|
"""
|
|
|
|
@pytest.fixture
|
|
def transport(self):
|
|
# Register a mock anthropic transport via the registry API.
|
|
from agent.transports.base import ProviderTransport
|
|
from agent.transports import register_transport
|
|
|
|
class _MockAnthropicTransport(ProviderTransport):
|
|
@property
|
|
def api_mode(self):
|
|
return "anthropic_messages"
|
|
def convert_messages(self, messages, **kw):
|
|
return messages
|
|
def convert_tools(self, tools, **kw):
|
|
return tools
|
|
def convert_response(self, resp, **kw):
|
|
return resp
|
|
def build_kwargs(self, model, messages, tools=None, **params):
|
|
return {}
|
|
def normalize_response(self, response, **kw):
|
|
return NormalizedResponse(content=None, tool_calls=None, finish_reason="stop")
|
|
|
|
register_transport("anthropic_messages", _MockAnthropicTransport)
|
|
return get_transport("anthropic_messages")
|
|
|
|
def test_api_mode(self, transport):
|
|
assert transport.api_mode == "anthropic_messages"
|