test: use real tool runtimes responses and plugin entities (#43142)

This commit is contained in:
Asuka Minato authored and GitHub committed 2026-09-29 07:08:42 +00:00
1 parent 38f42f357e
commit 948fd166dd
5 files changed
+102 -114

No files matched your search

@@ -29,13 +29,24 @@ from core.tools.entities.tool_entities import ToolIdentity, ToolProviderIdentity
@pytest.fixture
def mock_identity(mocker: MockerFixture):
return mocker.MagicMock(spec=AgentStrategyIdentity)
def strategy_identity() -> AgentStrategyIdentity:
return AgentStrategyIdentity(
author="test",
name="test-strategy",
label=I18nObject(en_US="Test strategy"),
provider="test-provider",
)
@pytest.fixture
def mock_provider_identity(mocker: MockerFixture):
return mocker.MagicMock(spec=AgentStrategyProviderIdentity)
def provider_identity() -> AgentStrategyProviderIdentity:
return AgentStrategyProviderIdentity(
author="test",
name="test-provider",
description=I18nObject(en_US="Test provider description"),
icon="icon.svg",
label=I18nObject(en_US="Test provider"),
)
# =========================================================
@@ -176,22 +187,22 @@ class TestAgentStrategyParameter:
class TestAgentStrategyProviderEntity:
def test_creation_with_plugin_id(self, mock_provider_identity) -> None:
def test_creation_with_plugin_id(self, provider_identity) -> None:
entity = AgentStrategyProviderEntity(
identity=mock_provider_identity,
identity=provider_identity,
plugin_id="plugin-123",
)
assert entity.plugin_id == "plugin-123"
def test_creation_with_empty_plugin_id(self, mock_provider_identity) -> None:
def test_creation_with_empty_plugin_id(self, provider_identity) -> None:
entity = AgentStrategyProviderEntity(
identity=mock_provider_identity,
identity=provider_identity,
plugin_id="",
)
assert entity.plugin_id == ""
def test_creation_without_plugin_id(self, mock_provider_identity) -> None:
entity = AgentStrategyProviderEntity(identity=mock_provider_identity)
def test_creation_without_plugin_id(self, provider_identity) -> None:
entity = AgentStrategyProviderEntity(identity=provider_identity)
assert entity.plugin_id is None
def test_invalid_identity_raises(self) -> None:
@@ -205,22 +216,22 @@ class TestAgentStrategyProviderEntity:
class TestAgentStrategyEntity:
def test_parameters_default_empty(self, mock_identity) -> None:
def test_parameters_default_empty(self, strategy_identity) -> None:
entity = AgentStrategyEntity(
identity=mock_identity,
identity=strategy_identity,
description=I18nObject(en_US="test"),
)
assert entity.parameters == []
def test_parameters_none_converted_to_empty(self, mock_identity) -> None:
def test_parameters_none_converted_to_empty(self, strategy_identity) -> None:
entity = AgentStrategyEntity(
identity=mock_identity,
identity=strategy_identity,
description=I18nObject(en_US="test"),
parameters=None,
)
assert entity.parameters == []
def test_parameters_preserved(self, mock_identity) -> None:
def test_parameters_preserved(self, strategy_identity) -> None:
param = AgentStrategyParameter.model_construct(
type=AgentStrategyParameter.AgentStrategyParameterType.STRING,
name="test",
@@ -228,16 +239,16 @@ class TestAgentStrategyEntity:
)
entity = AgentStrategyEntity(
identity=mock_identity,
identity=strategy_identity,
description=I18nObject(en_US="test"),
parameters=[param],
)
assert entity.parameters == [param]
def test_invalid_parameters_type_raises(self, mock_identity) -> None:
def test_invalid_parameters_type_raises(self, strategy_identity) -> None:
with pytest.raises(ValidationError):
AgentStrategyEntity(
identity=mock_identity,
identity=strategy_identity,
description=I18nObject(en_US="test"),
parameters="invalid",
)
@@ -250,25 +261,25 @@ class TestAgentStrategyEntity:
[AgentFeature.HISTORY_MESSAGES],
],
)
def test_features_valid(self, mock_identity, features) -> None:
def test_features_valid(self, strategy_identity, features) -> None:
entity = AgentStrategyEntity(
identity=mock_identity,
identity=strategy_identity,
description=I18nObject(en_US="test"),
features=features,
)
assert entity.features == features
def test_invalid_features_type_raises(self, mock_identity) -> None:
def test_invalid_features_type_raises(self, strategy_identity) -> None:
with pytest.raises(ValidationError):
AgentStrategyEntity(
identity=mock_identity,
identity=strategy_identity,
description=I18nObject(en_US="test"),
features="invalid",
)
def test_output_schema_and_meta_version(self, mock_identity) -> None:
def test_output_schema_and_meta_version(self, strategy_identity) -> None:
entity = AgentStrategyEntity(
identity=mock_identity,
identity=strategy_identity,
description=I18nObject(en_US="test"),
output_schema={"type": "object"},
meta_version="v1",
@@ -276,9 +287,9 @@ class TestAgentStrategyEntity:
assert entity.output_schema == {"type": "object"}
assert entity.meta_version == "v1"
def test_missing_required_fields_raise(self, mock_identity) -> None:
def test_missing_required_fields_raise(self, strategy_identity) -> None:
with pytest.raises(ValidationError):
AgentStrategyEntity(identity=mock_identity)
AgentStrategyEntity(identity=strategy_identity)
# =========================================================
@@ -287,27 +298,27 @@ class TestAgentStrategyEntity:
class TestAgentProviderEntityWithPlugin:
def test_default_strategies_empty(self, mock_provider_identity) -> None:
entity = AgentProviderEntityWithPlugin(identity=mock_provider_identity)
def test_default_strategies_empty(self, provider_identity) -> None:
entity = AgentProviderEntityWithPlugin(identity=provider_identity)
assert entity.strategies == []
def test_strategies_assignment(self, mock_provider_identity, mock_identity) -> None:
def test_strategies_assignment(self, provider_identity, strategy_identity) -> None:
strategy = AgentStrategyEntity.model_construct(
identity=mock_identity,
identity=strategy_identity,
description=I18nObject(en_US="test"),
parameters=[],
)
entity = AgentProviderEntityWithPlugin(
identity=mock_provider_identity,
identity=provider_identity,
strategies=[strategy],
)
assert entity.strategies == [strategy]
def test_invalid_strategies_type_raises(self, mock_provider_identity) -> None:
def test_invalid_strategies_type_raises(self, provider_identity) -> None:
with pytest.raises(ValidationError):
AgentProviderEntityWithPlugin(
identity=mock_provider_identity,
identity=provider_identity,
strategies="invalid",
)
+42 -34
View File
@@ -1,13 +1,22 @@
"""Unit tests for MCP entities module."""
from unittest.mock import Mock
from queue import Queue
from core.mcp.entities import (
SUPPORTED_PROTOCOL_VERSIONS,
RequestContext,
)
from core.mcp.session.base_session import BaseSession
from core.mcp.types import LATEST_PROTOCOL_VERSION, RequestParams
from core.mcp.types import LATEST_PROTOCOL_VERSION, RequestParams, ServerNotification, ServerRequest
def _session() -> BaseSession:
return BaseSession(
read_stream=Queue(),
write_stream=Queue(),
receive_request_type=ServerRequest,
receive_notification_type=ServerNotification,
)
class TestProtocolVersions:
@@ -31,46 +40,46 @@ class TestRequestContext:
def test_request_context_creation(self):
"""Test creating a RequestContext instance."""
mock_session = Mock(spec=BaseSession)
mock_lifespan = {"key": "value"}
mock_meta = RequestParams.Meta(progressToken="test-token")
session = _session()
lifespan = {"key": "value"}
meta = RequestParams.Meta(progressToken="test-token")
context = RequestContext(
request_id="test-request-123",
meta=mock_meta,
session=mock_session,
lifespan_context=mock_lifespan,
meta=meta,
session=session,
lifespan_context=lifespan,
)
assert context.request_id == "test-request-123"
assert context.meta == mock_meta
assert context.session == mock_session
assert context.lifespan_context == mock_lifespan
assert context.meta == meta
assert context.session == session
assert context.lifespan_context == lifespan
def test_request_context_with_none_meta(self):
"""Test creating RequestContext with None meta."""
mock_session = Mock(spec=BaseSession)
session = _session()
context = RequestContext(
request_id=42, # Can be int or string
meta=None,
session=mock_session,
session=session,
lifespan_context=None,
)
assert context.request_id == 42
assert context.meta is None
assert context.session == mock_session
assert context.session == session
assert context.lifespan_context is None
def test_request_context_attributes(self):
"""Test RequestContext attributes are accessible."""
mock_session = Mock(spec=BaseSession)
session = _session()
context = RequestContext(
request_id="test-123",
meta=None,
session=mock_session,
session=session,
lifespan_context=None,
)
@@ -83,19 +92,19 @@ class TestRequestContext:
# Verify values
assert context.request_id == "test-123"
assert context.meta is None
assert context.session == mock_session
assert context.session == session
assert context.lifespan_context is None
def test_request_context_generic_typing(self):
"""Test RequestContext with different generic types."""
# Create a mock session with specific type
mock_session = Mock(spec=BaseSession)
# Create a real session without starting its receive loop.
session = _session()
# Create context with string lifespan context
context_str = RequestContext[BaseSession, str](
request_id="test-1",
meta=None,
session=mock_session,
session=session,
lifespan_context="string-context",
)
assert isinstance(context_str.lifespan_context, str)
@@ -104,7 +113,7 @@ class TestRequestContext:
context_dict = RequestContext[BaseSession, dict](
request_id="test-2",
meta=None,
session=mock_session,
session=session,
lifespan_context={"key": "value"},
)
assert isinstance(context_dict.lifespan_context, dict)
@@ -118,7 +127,7 @@ class TestRequestContext:
context_custom = RequestContext[BaseSession, CustomLifespan](
request_id="test-3",
meta=None,
session=mock_session,
session=session,
lifespan_context=custom_lifespan,
)
assert isinstance(context_custom.lifespan_context, CustomLifespan)
@@ -126,13 +135,13 @@ class TestRequestContext:
def test_request_context_with_progress_meta(self):
"""Test RequestContext with progress metadata."""
mock_session = Mock(spec=BaseSession)
session = _session()
progress_meta = RequestParams.Meta(progressToken="progress-123")
context = RequestContext(
request_id="req-456",
meta=progress_meta,
session=mock_session,
session=session,
lifespan_context=None,
)
@@ -141,27 +150,27 @@ class TestRequestContext:
def test_request_context_equality(self):
"""Test RequestContext equality comparison."""
mock_session1 = Mock(spec=BaseSession)
mock_session2 = Mock(spec=BaseSession)
session1 = _session()
session2 = _session()
context1 = RequestContext(
request_id="test-123",
meta=None,
session=mock_session1,
session=session1,
lifespan_context="context",
)
context2 = RequestContext(
request_id="test-123",
meta=None,
session=mock_session1,
session=session1,
lifespan_context="context",
)
context3 = RequestContext(
request_id="test-456",
meta=None,
session=mock_session1,
session=session1,
lifespan_context="context",
)
@@ -175,24 +184,23 @@ class TestRequestContext:
context4 = RequestContext(
request_id="test-123",
meta=None,
session=mock_session2,
session=session2,
lifespan_context="context",
)
assert context1 != context4
def test_request_context_repr(self):
"""Test RequestContext string representation."""
mock_session = Mock(spec=BaseSession)
mock_session.__repr__ = Mock(return_value="<MockSession>")
session = _session()
context = RequestContext(
request_id="test-123",
meta=None,
session=mock_session,
session=session,
lifespan_context={"data": "test"},
)
repr_str = repr(context)
assert "RequestContext" in repr_str
assert "test-123" in repr_str
assert "MockSession" in repr_str
assert repr(session) in repr_str
+2 -5
View File
@@ -44,11 +44,8 @@ class MockBrandingService:
self.application_title = application_title
def get_branding_config(self) -> BrandingModel:
"""Return mock branding configuration"""
branding_model = MagicMock(spec=BrandingModel)
branding_model.enabled = self.enabled
branding_model.application_title = self.application_title
return branding_model
"""Return branding configuration with the requested test settings."""
return BrandingModel(enabled=self.enabled, application_title=self.application_title)
class MockEmailSender:
+12 -39
View File
@@ -54,15 +54,13 @@ class TestApiToolInvoke:
author="test_author",
)
# Create a mock runtime
self.mock_runtime = Mock(spec=ToolRuntime)
self.mock_runtime.credentials = {"auth_type": "none"}
self.runtime = ToolRuntime(tenant_id="test_tenant", credentials={"auth_type": "none"})
# Create the ApiTool instance
self.api_tool = ApiTool(
entity=self.mock_tool_entity,
api_bundle=self.mock_api_bundle,
runtime=self.mock_runtime,
runtime=self.runtime,
provider_id="test_provider",
)
@@ -71,19 +69,13 @@ class TestApiToolInvoke:
self, mock_get: Mock, database_session: Session
) -> None:
"""Test that when upstream returns JSON, the output Text message contains JSON-serialized string."""
# Setup mock response with JSON content
# Use the response's real JSON parsing and content decoding.
json_response_data = {
"key": "value",
"number": 123,
"nested": {"inner": "data"},
}
mock_response = Mock(spec=httpx.Response)
mock_response.status_code = 200
mock_response.content = json.dumps(json_response_data).encode("utf-8")
mock_response.json.return_value = json_response_data
mock_response.text = json.dumps(json_response_data)
mock_response.headers = {"content-type": "application/json"}
mock_get.return_value = mock_response
mock_get.return_value = httpx.Response(200, json=json_response_data)
# Invoke the tool
result_generator = self.api_tool._invoke(session=database_session, user_id="test_user", tool_parameters={})
@@ -150,15 +142,11 @@ class TestApiToolInvoke:
self, mock_get: Mock, test_case, database_session: Session
) -> None:
"""Test that when upstream returns a non-dict JSON, the output Text message contains JSON-serialized string."""
# Setup mock response with non-dict JSON content
# Explicit content also covers JSON null, which is distinct from no body.
_, json_value = test_case
mock_response = Mock(spec=httpx.Response)
mock_response.status_code = 200
mock_response.content = json.dumps(json_value).encode("utf-8")
mock_response.json.return_value = json_value
mock_response.text = json.dumps(json_value)
mock_response.headers = {"content-type": "application/json"}
mock_get.return_value = mock_response
mock_get.return_value = httpx.Response(
200, content=json.dumps(json_value), headers={"content-type": "application/json"}
)
# Invoke the tool
result_generator = self.api_tool._invoke(session=database_session, user_id="test_user", tool_parameters={})
@@ -189,15 +177,9 @@ class TestApiToolInvoke:
self, mock_get: Mock, database_session: Session
) -> None:
"""Test that when upstream returns plain text, the output Text message contains the original text."""
# Setup mock response with plain text content
# Plain text exercises the response's real JSON decoding failure.
text_response_data = "This is a plain text response"
mock_response = Mock(spec=httpx.Response)
mock_response.status_code = 200
mock_response.content = text_response_data.encode("utf-8")
mock_response.json.side_effect = json.JSONDecodeError("Expecting value", "doc", 0)
mock_response.text = text_response_data
mock_response.headers = {"content-type": "text/plain"}
mock_get.return_value = mock_response
mock_get.return_value = httpx.Response(200, text=text_response_data)
# Invoke the tool
result_generator = self.api_tool._invoke(session=database_session, user_id="test_user", tool_parameters={})
@@ -218,12 +200,7 @@ class TestApiToolInvoke:
@patch("core.tools.custom_tool.tool.ssrf_proxy.get")
def test_invoke_with_empty_response(self, mock_get: Mock, database_session: Session) -> None:
"""Test that empty responses are handled correctly."""
# Setup mock response with empty content
mock_response = Mock(spec=httpx.Response)
mock_response.status_code = 200
mock_response.content = b""
mock_response.headers = {"content-type": "application/json"}
mock_get.return_value = mock_response
mock_get.return_value = httpx.Response(200, content=b"", headers={"content-type": "application/json"})
# Invoke the tool
result_generator = self.api_tool._invoke(session=database_session, user_id="test_user", tool_parameters={})
@@ -244,11 +221,7 @@ class TestApiToolInvoke:
@patch("core.tools.custom_tool.tool.ssrf_proxy.get")
def test_invoke_with_error_response(self, mock_get: Mock, database_session: Session) -> None:
"""Test that error responses are handled correctly."""
# Setup mock response with error status code
mock_response = Mock(spec=httpx.Response)
mock_response.status_code = 404
mock_response.text = "Not Found"
mock_get.return_value = mock_response
mock_get.return_value = httpx.Response(404, text="Not Found")
result_generator = self.api_tool._invoke(session=database_session, user_id="test_user", tool_parameters={})
+2 -3
View File
@@ -2,7 +2,7 @@ import base64
from collections.abc import Iterator
from decimal import Decimal
from typing import Any
from unittest.mock import Mock, patch
from unittest.mock import patch
import pytest
from sqlalchemy.engine import Engine
@@ -39,8 +39,7 @@ def _make_mcp_tool(output_schema: dict[str, Any] | None = None) -> MCPTool:
provider="test_provider",
)
entity = ToolEntity(identity=identity, output_schema=output_schema or {})
runtime = Mock(spec=ToolRuntime)
runtime.credentials = {}
runtime = ToolRuntime(tenant_id="test_tenant")
return MCPTool(
entity=entity,
runtime=runtime,