mirror of
https://github.com/langgenius/dify.git
synced 2026-10-04 13:47:55 +08:00
test: use real plugin runtime HTTP and stream responses
This commit is contained in:
1 parent
eb80de9280
commit
77a301f8ff
1 file changed
+124
-212
@@ -13,7 +13,7 @@ Tests follow the Arrange-Act-Assert pattern for clarity.
|
||||
|
||||
import json
|
||||
from typing import Any
|
||||
from unittest.mock import MagicMock, patch
|
||||
from unittest.mock import patch
|
||||
|
||||
import httpx
|
||||
import pytest
|
||||
@@ -54,8 +54,17 @@ def _patch_shared_httpx_client():
|
||||
After refactor, code uses core.plugin.impl.base._httpx_client directly.
|
||||
Patch its request/stream to route through module-level httpx so existing mocks still apply.
|
||||
"""
|
||||
|
||||
def request[**P](*args: P.args, **kwargs: P.kwargs) -> httpx.Response:
|
||||
response = httpx.request(*args, **kwargs)
|
||||
method, url = kwargs["method"], kwargs["url"]
|
||||
assert isinstance(method, str)
|
||||
assert isinstance(url, str)
|
||||
response.request = httpx.Request(method, url)
|
||||
return response
|
||||
|
||||
with (
|
||||
patch("core.plugin.impl.base._httpx_client.request", side_effect=lambda **kw: httpx.request(**kw)),
|
||||
patch("core.plugin.impl.base._httpx_client.request", side_effect=request),
|
||||
patch("core.plugin.impl.base._httpx_client.stream", side_effect=lambda **kw: httpx.stream(**kw)),
|
||||
):
|
||||
yield
|
||||
@@ -124,9 +133,7 @@ class TestPluginRuntimeExecution:
|
||||
def test_successful_request_execution(self, plugin_client, mock_config):
|
||||
"""Test successful HTTP request execution."""
|
||||
# Arrange
|
||||
mock_response = MagicMock()
|
||||
mock_response.status_code = 200
|
||||
mock_response.json.return_value = {"result": "success"}
|
||||
mock_response = httpx.Response(200, json={"result": "success"})
|
||||
|
||||
with patch("httpx.request", return_value=mock_response, autospec=True) as mock_request:
|
||||
# Act
|
||||
@@ -143,8 +150,7 @@ class TestPluginRuntimeExecution:
|
||||
def test_request_with_timeout_configuration(self, plugin_client, mock_config):
|
||||
"""Test that timeout configuration is properly applied."""
|
||||
# Arrange
|
||||
mock_response = MagicMock()
|
||||
mock_response.status_code = 200
|
||||
mock_response = httpx.Response(200)
|
||||
|
||||
with patch("httpx.request", return_value=mock_response, autospec=True) as mock_request:
|
||||
# Act
|
||||
@@ -155,7 +161,7 @@ class TestPluginRuntimeExecution:
|
||||
assert "timeout" in call_kwargs
|
||||
|
||||
def test_request_timeout_can_be_scoped_to_current_context(self, plugin_client, mock_config):
|
||||
mock_response = MagicMock(status_code=200)
|
||||
mock_response = httpx.Response(200)
|
||||
|
||||
with (
|
||||
patch("core.plugin.impl.base.plugin_daemon_request_timeout", httpx.Timeout(600.0)),
|
||||
@@ -215,9 +221,7 @@ class TestPluginRuntimeSandboxIsolation:
|
||||
def test_api_key_authentication(self, plugin_client, mock_config):
|
||||
"""Test that all requests include API key for authentication."""
|
||||
# Arrange
|
||||
mock_response = MagicMock()
|
||||
mock_response.status_code = 200
|
||||
mock_response.json.return_value = {"code": 0, "message": "", "data": True}
|
||||
mock_response = httpx.Response(200, json={"code": 0, "message": "", "data": True})
|
||||
|
||||
with patch("httpx.request", return_value=mock_response, autospec=True) as mock_request:
|
||||
# Act
|
||||
@@ -234,9 +238,7 @@ class TestPluginRuntimeSandboxIsolation:
|
||||
class TestResponse(BaseModel):
|
||||
result: str
|
||||
|
||||
mock_response = MagicMock()
|
||||
mock_response.status_code = 200
|
||||
mock_response.json.return_value = {"code": 0, "message": "", "data": {"result": "isolated_execution"}}
|
||||
mock_response = httpx.Response(200, json={"code": 0, "message": "", "data": {"result": "isolated_execution"}})
|
||||
|
||||
with patch("httpx.request", return_value=mock_response, autospec=True):
|
||||
# Act
|
||||
@@ -250,10 +252,8 @@ class TestPluginRuntimeSandboxIsolation:
|
||||
def test_plugin_daemon_unauthorized_error(self, plugin_client, mock_config):
|
||||
"""Test handling of unauthorized access to plugin daemon."""
|
||||
# Arrange
|
||||
mock_response = MagicMock()
|
||||
mock_response.status_code = 200
|
||||
error_message = json.dumps({"error_type": "PluginDaemonUnauthorizedError", "message": "Unauthorized access"})
|
||||
mock_response.json.return_value = {"code": -1, "message": error_message, "data": None}
|
||||
mock_response = httpx.Response(200, json={"code": -1, "message": error_message, "data": None})
|
||||
|
||||
with patch("httpx.request", return_value=mock_response, autospec=True):
|
||||
# Act & Assert
|
||||
@@ -264,12 +264,10 @@ class TestPluginRuntimeSandboxIsolation:
|
||||
def test_plugin_permission_denied(self, plugin_client, mock_config):
|
||||
"""Test handling of permission denied errors."""
|
||||
# Arrange
|
||||
mock_response = MagicMock()
|
||||
mock_response.status_code = 200
|
||||
error_message = json.dumps(
|
||||
{"error_type": "PluginPermissionDeniedError", "message": "Permission denied for this operation"}
|
||||
)
|
||||
mock_response.json.return_value = {"code": -1, "message": error_message, "data": None}
|
||||
mock_response = httpx.Response(200, json={"code": -1, "message": error_message, "data": None})
|
||||
|
||||
with patch("httpx.request", return_value=mock_response, autospec=True):
|
||||
# Act & Assert
|
||||
@@ -306,8 +304,7 @@ class TestPluginRuntimeResourceLimits:
|
||||
def test_timeout_configuration_applied(self, plugin_client, mock_config):
|
||||
"""Test that timeout configuration is properly applied to requests."""
|
||||
# Arrange
|
||||
mock_response = MagicMock()
|
||||
mock_response.status_code = 200
|
||||
mock_response = httpx.Response(200)
|
||||
|
||||
with patch("httpx.request", return_value=mock_response, autospec=True) as mock_request:
|
||||
# Act
|
||||
@@ -338,12 +335,10 @@ class TestPluginRuntimeResourceLimits:
|
||||
def test_resource_limit_error_from_daemon(self, plugin_client, mock_config):
|
||||
"""Test handling of resource limit errors from plugin daemon."""
|
||||
# Arrange
|
||||
mock_response = MagicMock()
|
||||
mock_response.status_code = 200
|
||||
error_message = json.dumps(
|
||||
{"error_type": "PluginDaemonInternalServerError", "message": "Resource limit exceeded"}
|
||||
)
|
||||
mock_response.json.return_value = {"code": -1, "message": error_message, "data": None}
|
||||
mock_response = httpx.Response(200, json={"code": -1, "message": error_message, "data": None})
|
||||
|
||||
with patch("httpx.request", return_value=mock_response, autospec=True):
|
||||
# Act & Assert
|
||||
@@ -378,15 +373,13 @@ class TestPluginRuntimeErrorHandling:
|
||||
def test_plugin_invoke_rate_limit_error(self, plugin_client, mock_config):
|
||||
"""Test handling of rate limit errors during plugin invocation."""
|
||||
# Arrange
|
||||
mock_response = MagicMock()
|
||||
mock_response.status_code = 200
|
||||
invoke_error = {
|
||||
"error_type": "InvokeRateLimitError",
|
||||
"message": "Rate limit exceeded",
|
||||
"args": {"description": "Rate limit exceeded"},
|
||||
}
|
||||
error_message = json.dumps({"error_type": "PluginInvokeError", "message": json.dumps(invoke_error)})
|
||||
mock_response.json.return_value = {"code": -1, "message": error_message, "data": None}
|
||||
mock_response = httpx.Response(200, json={"code": -1, "message": error_message, "data": None})
|
||||
|
||||
with patch("httpx.request", return_value=mock_response, autospec=True):
|
||||
# Act & Assert
|
||||
@@ -397,15 +390,13 @@ class TestPluginRuntimeErrorHandling:
|
||||
def test_plugin_invoke_authorization_error(self, plugin_client, mock_config):
|
||||
"""Test handling of authorization errors during plugin invocation."""
|
||||
# Arrange
|
||||
mock_response = MagicMock()
|
||||
mock_response.status_code = 200
|
||||
invoke_error = {
|
||||
"error_type": "InvokeAuthorizationError",
|
||||
"message": "Invalid credentials",
|
||||
"args": {"description": "Invalid credentials"},
|
||||
}
|
||||
error_message = json.dumps({"error_type": "PluginInvokeError", "message": json.dumps(invoke_error)})
|
||||
mock_response.json.return_value = {"code": -1, "message": error_message, "data": None}
|
||||
mock_response = httpx.Response(200, json={"code": -1, "message": error_message, "data": None})
|
||||
|
||||
with patch("httpx.request", return_value=mock_response, autospec=True):
|
||||
# Act & Assert
|
||||
@@ -416,15 +407,13 @@ class TestPluginRuntimeErrorHandling:
|
||||
def test_plugin_invoke_bad_request_error(self, plugin_client, mock_config):
|
||||
"""Test handling of bad request errors during plugin invocation."""
|
||||
# Arrange
|
||||
mock_response = MagicMock()
|
||||
mock_response.status_code = 200
|
||||
invoke_error = {
|
||||
"error_type": "InvokeBadRequestError",
|
||||
"message": "Invalid parameters",
|
||||
"args": {"description": "Invalid parameters"},
|
||||
}
|
||||
error_message = json.dumps({"error_type": "PluginInvokeError", "message": json.dumps(invoke_error)})
|
||||
mock_response.json.return_value = {"code": -1, "message": error_message, "data": None}
|
||||
mock_response = httpx.Response(200, json={"code": -1, "message": error_message, "data": None})
|
||||
|
||||
with patch("httpx.request", return_value=mock_response, autospec=True):
|
||||
# Act & Assert
|
||||
@@ -435,15 +424,13 @@ class TestPluginRuntimeErrorHandling:
|
||||
def test_plugin_invoke_connection_error(self, plugin_client, mock_config):
|
||||
"""Test handling of connection errors during plugin invocation."""
|
||||
# Arrange
|
||||
mock_response = MagicMock()
|
||||
mock_response.status_code = 200
|
||||
invoke_error = {
|
||||
"error_type": "InvokeConnectionError",
|
||||
"message": "Connection to external service failed",
|
||||
"args": {"description": "Connection to external service failed"},
|
||||
}
|
||||
error_message = json.dumps({"error_type": "PluginInvokeError", "message": json.dumps(invoke_error)})
|
||||
mock_response.json.return_value = {"code": -1, "message": error_message, "data": None}
|
||||
mock_response = httpx.Response(200, json={"code": -1, "message": error_message, "data": None})
|
||||
|
||||
with patch("httpx.request", return_value=mock_response, autospec=True):
|
||||
# Act & Assert
|
||||
@@ -454,15 +441,13 @@ class TestPluginRuntimeErrorHandling:
|
||||
def test_plugin_invoke_server_unavailable_error(self, plugin_client, mock_config):
|
||||
"""Test handling of server unavailable errors during plugin invocation."""
|
||||
# Arrange
|
||||
mock_response = MagicMock()
|
||||
mock_response.status_code = 200
|
||||
invoke_error = {
|
||||
"error_type": "InvokeServerUnavailableError",
|
||||
"message": "Service temporarily unavailable",
|
||||
"args": {"description": "Service temporarily unavailable"},
|
||||
}
|
||||
error_message = json.dumps({"error_type": "PluginInvokeError", "message": json.dumps(invoke_error)})
|
||||
mock_response.json.return_value = {"code": -1, "message": error_message, "data": None}
|
||||
mock_response = httpx.Response(200, json={"code": -1, "message": error_message, "data": None})
|
||||
|
||||
with patch("httpx.request", return_value=mock_response, autospec=True):
|
||||
# Act & Assert
|
||||
@@ -473,14 +458,12 @@ class TestPluginRuntimeErrorHandling:
|
||||
def test_credentials_validation_error(self, plugin_client, mock_config):
|
||||
"""Test handling of credential validation errors."""
|
||||
# Arrange
|
||||
mock_response = MagicMock()
|
||||
mock_response.status_code = 200
|
||||
invoke_error = {
|
||||
"error_type": "CredentialsValidateFailedError",
|
||||
"message": "Invalid API key format",
|
||||
}
|
||||
error_message = json.dumps({"error_type": "PluginInvokeError", "message": json.dumps(invoke_error)})
|
||||
mock_response.json.return_value = {"code": -1, "message": error_message, "data": None}
|
||||
mock_response = httpx.Response(200, json={"code": -1, "message": error_message, "data": None})
|
||||
|
||||
with patch("httpx.request", return_value=mock_response, autospec=True):
|
||||
# Act & Assert
|
||||
@@ -491,12 +474,10 @@ class TestPluginRuntimeErrorHandling:
|
||||
def test_plugin_not_found_error(self, plugin_client, mock_config):
|
||||
"""Test handling of plugin not found errors."""
|
||||
# Arrange
|
||||
mock_response = MagicMock()
|
||||
mock_response.status_code = 200
|
||||
error_message = json.dumps(
|
||||
{"error_type": "PluginNotFoundError", "message": "Plugin with ID 'test-plugin' not found"}
|
||||
)
|
||||
mock_response.json.return_value = {"code": -1, "message": error_message, "data": None}
|
||||
mock_response = httpx.Response(200, json={"code": -1, "message": error_message, "data": None})
|
||||
|
||||
with patch("httpx.request", return_value=mock_response, autospec=True):
|
||||
# Act & Assert
|
||||
@@ -507,12 +488,10 @@ class TestPluginRuntimeErrorHandling:
|
||||
def test_plugin_unique_identifier_error(self, plugin_client, mock_config):
|
||||
"""Test handling of unique identifier errors."""
|
||||
# Arrange
|
||||
mock_response = MagicMock()
|
||||
mock_response.status_code = 200
|
||||
error_message = json.dumps(
|
||||
{"error_type": "PluginUniqueIdentifierError", "message": "Invalid plugin identifier format"}
|
||||
)
|
||||
mock_response.json.return_value = {"code": -1, "message": error_message, "data": None}
|
||||
mock_response = httpx.Response(200, json={"code": -1, "message": error_message, "data": None})
|
||||
|
||||
with patch("httpx.request", return_value=mock_response, autospec=True):
|
||||
# Act & Assert
|
||||
@@ -523,12 +502,10 @@ class TestPluginRuntimeErrorHandling:
|
||||
def test_daemon_bad_request_error(self, plugin_client, mock_config):
|
||||
"""Test handling of daemon bad request errors."""
|
||||
# Arrange
|
||||
mock_response = MagicMock()
|
||||
mock_response.status_code = 200
|
||||
error_message = json.dumps(
|
||||
{"error_type": "PluginDaemonBadRequestError", "message": "Missing required parameter"}
|
||||
)
|
||||
mock_response.json.return_value = {"code": -1, "message": error_message, "data": None}
|
||||
mock_response = httpx.Response(200, json={"code": -1, "message": error_message, "data": None})
|
||||
|
||||
with patch("httpx.request", return_value=mock_response, autospec=True):
|
||||
# Act & Assert
|
||||
@@ -539,10 +516,8 @@ class TestPluginRuntimeErrorHandling:
|
||||
def test_daemon_not_found_error(self, plugin_client, mock_config):
|
||||
"""Test handling of daemon not found errors."""
|
||||
# Arrange
|
||||
mock_response = MagicMock()
|
||||
mock_response.status_code = 200
|
||||
error_message = json.dumps({"error_type": "PluginDaemonNotFoundError", "message": "Resource not found"})
|
||||
mock_response.json.return_value = {"code": -1, "message": error_message, "data": None}
|
||||
mock_response = httpx.Response(200, json={"code": -1, "message": error_message, "data": None})
|
||||
|
||||
with patch("httpx.request", return_value=mock_response, autospec=True):
|
||||
# Act & Assert
|
||||
@@ -553,14 +528,12 @@ class TestPluginRuntimeErrorHandling:
|
||||
def test_generic_plugin_invoke_error(self, plugin_client, mock_config):
|
||||
"""Test handling of generic plugin invoke errors."""
|
||||
# Arrange
|
||||
mock_response = MagicMock()
|
||||
mock_response.status_code = 200
|
||||
# Create a proper nested JSON structure for PluginInvokeError
|
||||
invoke_error_message = json.dumps(
|
||||
{"error_type": "UnknownInvokeError", "message": "Generic plugin execution error"}
|
||||
)
|
||||
error_message = json.dumps({"error_type": "PluginInvokeError", "message": invoke_error_message})
|
||||
mock_response.json.return_value = {"code": -1, "message": error_message, "data": None}
|
||||
mock_response = httpx.Response(200, json={"code": -1, "message": error_message, "data": None})
|
||||
|
||||
with patch("httpx.request", return_value=mock_response, autospec=True):
|
||||
# Act & Assert
|
||||
@@ -571,10 +544,8 @@ class TestPluginRuntimeErrorHandling:
|
||||
def test_unknown_error_type(self, plugin_client, mock_config):
|
||||
"""Test handling of unknown error types."""
|
||||
# Arrange
|
||||
mock_response = MagicMock()
|
||||
mock_response.status_code = 200
|
||||
error_message = json.dumps({"error_type": "UnknownErrorType", "message": "Unknown error occurred"})
|
||||
mock_response.json.return_value = {"code": -1, "message": error_message, "data": None}
|
||||
mock_response = httpx.Response(200, json={"code": -1, "message": error_message, "data": None})
|
||||
|
||||
with patch("httpx.request", return_value=mock_response, autospec=True):
|
||||
# Act & Assert
|
||||
@@ -585,10 +556,8 @@ class TestPluginRuntimeErrorHandling:
|
||||
def test_http_status_error_handling(self, plugin_client, mock_config):
|
||||
"""Test handling of HTTP status errors."""
|
||||
# Arrange
|
||||
mock_response = MagicMock()
|
||||
mock_response.status_code = 500
|
||||
mock_response.raise_for_status.side_effect = httpx.HTTPStatusError(
|
||||
"Server Error", request=MagicMock(), response=mock_response
|
||||
mock_response = httpx.Response(
|
||||
500, request=httpx.Request("GET", "http://127.0.0.1:5002/plugin/test-tenant/test")
|
||||
)
|
||||
|
||||
with patch("httpx.request", return_value=mock_response, autospec=True):
|
||||
@@ -599,9 +568,7 @@ class TestPluginRuntimeErrorHandling:
|
||||
def test_empty_data_response_error(self, plugin_client, mock_config):
|
||||
"""Test handling of empty data in successful response."""
|
||||
# Arrange
|
||||
mock_response = MagicMock()
|
||||
mock_response.status_code = 200
|
||||
mock_response.json.return_value = {"code": 0, "message": "", "data": None}
|
||||
mock_response = httpx.Response(200, json={"code": 0, "message": "", "data": None})
|
||||
|
||||
with patch("httpx.request", return_value=mock_response, autospec=True):
|
||||
# Act & Assert
|
||||
@@ -641,9 +608,7 @@ class TestPluginRuntimeCommunication:
|
||||
value: str
|
||||
count: int
|
||||
|
||||
mock_response = MagicMock()
|
||||
mock_response.status_code = 200
|
||||
mock_response.json.return_value = {"code": 0, "message": "", "data": {"value": "test", "count": 42}}
|
||||
mock_response = httpx.Response(200, json={"code": 0, "message": "", "data": {"value": "test", "count": 42}})
|
||||
|
||||
with patch("httpx.request", return_value=mock_response, autospec=True):
|
||||
# Act
|
||||
@@ -669,8 +634,7 @@ class TestPluginRuntimeCommunication:
|
||||
'data: {"code": 0, "message": "", "data": {"chunk": "third"}}',
|
||||
]
|
||||
|
||||
mock_response = MagicMock()
|
||||
mock_response.iter_lines.return_value = [line.encode("utf-8") for line in stream_data]
|
||||
mock_response = httpx.Response(200, text="\n".join(stream_data))
|
||||
|
||||
with patch("httpx.stream", autospec=True) as mock_stream:
|
||||
mock_stream.return_value.__enter__.return_value = mock_response
|
||||
@@ -699,8 +663,7 @@ class TestPluginRuntimeCommunication:
|
||||
f'data: {{"code": -500, "message": {json.dumps(error_obj)}, "data": null}}',
|
||||
]
|
||||
|
||||
mock_response = MagicMock()
|
||||
mock_response.iter_lines.return_value = [line.encode("utf-8") for line in stream_data]
|
||||
mock_response = httpx.Response(200, text="\n".join(stream_data))
|
||||
|
||||
with patch("httpx.stream", autospec=True) as mock_stream:
|
||||
mock_stream.return_value.__enter__.return_value = mock_response
|
||||
@@ -738,9 +701,7 @@ class TestPluginRuntimeCommunication:
|
||||
status: str
|
||||
data: dict[str, Any]
|
||||
|
||||
mock_response = MagicMock()
|
||||
mock_response.status_code = 200
|
||||
mock_response.json.return_value = {"status": "success", "data": {"key": "value"}}
|
||||
mock_response = httpx.Response(200, json={"status": "success", "data": {"key": "value"}})
|
||||
|
||||
with patch("httpx.request", return_value=mock_response, autospec=True):
|
||||
# Act
|
||||
@@ -764,8 +725,7 @@ class TestPluginRuntimeCommunication:
|
||||
'{"id": 2, "text": "second"}',
|
||||
]
|
||||
|
||||
mock_response = MagicMock()
|
||||
mock_response.iter_lines.return_value = [line.encode("utf-8") for line in stream_data]
|
||||
mock_response = httpx.Response(200, text="\n".join(stream_data))
|
||||
|
||||
with patch("httpx.stream", autospec=True) as mock_stream:
|
||||
mock_stream.return_value.__enter__.return_value = mock_response
|
||||
@@ -796,8 +756,7 @@ class TestPluginRuntimeCommunication:
|
||||
"",
|
||||
]
|
||||
|
||||
mock_response = MagicMock()
|
||||
mock_response.iter_lines.return_value = [line.encode("utf-8") for line in stream_data]
|
||||
mock_response = httpx.Response(200, text="\n".join(stream_data))
|
||||
|
||||
with patch("httpx.stream", autospec=True) as mock_stream:
|
||||
mock_stream.return_value.__enter__.return_value = mock_response
|
||||
@@ -845,8 +804,7 @@ class TestPluginToolManagerIntegration:
|
||||
'data: {"code": 0, "message": "", "data": {"type": "text", "message": {"text": "Result"}}}',
|
||||
]
|
||||
|
||||
mock_response = MagicMock()
|
||||
mock_response.iter_lines.return_value = [line.encode("utf-8") for line in stream_data]
|
||||
mock_response = httpx.Response(200, text="\n".join(stream_data))
|
||||
|
||||
with patch("httpx.stream", autospec=True) as mock_stream:
|
||||
mock_stream.return_value.__enter__.return_value = mock_response
|
||||
@@ -875,8 +833,7 @@ class TestPluginToolManagerIntegration:
|
||||
'data: {"code": 0, "message": "", "data": {"result": true}}',
|
||||
]
|
||||
|
||||
mock_response = MagicMock()
|
||||
mock_response.iter_lines.return_value = [line.encode("utf-8") for line in stream_data]
|
||||
mock_response = httpx.Response(200, text="\n".join(stream_data))
|
||||
|
||||
with patch("httpx.stream", autospec=True) as mock_stream:
|
||||
mock_stream.return_value.__enter__.return_value = mock_response
|
||||
@@ -899,8 +856,7 @@ class TestPluginToolManagerIntegration:
|
||||
'data: {"code": 0, "message": "", "data": {"result": false}}',
|
||||
]
|
||||
|
||||
mock_response = MagicMock()
|
||||
mock_response.iter_lines.return_value = [line.encode("utf-8") for line in stream_data]
|
||||
mock_response = httpx.Response(200, text="\n".join(stream_data))
|
||||
|
||||
with patch("httpx.stream", autospec=True) as mock_stream:
|
||||
mock_stream.return_value.__enter__.return_value = mock_response
|
||||
@@ -923,8 +879,7 @@ class TestPluginToolManagerIntegration:
|
||||
'data: {"code": 0, "message": "", "data": {"result": true}}',
|
||||
]
|
||||
|
||||
mock_response = MagicMock()
|
||||
mock_response.iter_lines.return_value = [line.encode("utf-8") for line in stream_data]
|
||||
mock_response = httpx.Response(200, text="\n".join(stream_data))
|
||||
|
||||
with patch("httpx.stream", autospec=True) as mock_stream:
|
||||
mock_stream.return_value.__enter__.return_value = mock_response
|
||||
@@ -967,16 +922,17 @@ class TestPluginInstallerIntegration:
|
||||
def test_list_plugins_success(self, installer, mock_config):
|
||||
"""Test successful plugin listing."""
|
||||
# Arrange
|
||||
mock_response = MagicMock()
|
||||
mock_response.status_code = 200
|
||||
mock_response.json.return_value = {
|
||||
"code": 0,
|
||||
"message": "",
|
||||
"data": {
|
||||
"list": [],
|
||||
"total": 0,
|
||||
mock_response = httpx.Response(
|
||||
200,
|
||||
json={
|
||||
"code": 0,
|
||||
"message": "",
|
||||
"data": {
|
||||
"list": [],
|
||||
"total": 0,
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
with patch("httpx.request", return_value=mock_response, autospec=True):
|
||||
# Act
|
||||
@@ -988,9 +944,7 @@ class TestPluginInstallerIntegration:
|
||||
def test_uninstall_plugin_success(self, installer, mock_config):
|
||||
"""Test successful plugin uninstallation."""
|
||||
# Arrange
|
||||
mock_response = MagicMock()
|
||||
mock_response.status_code = 200
|
||||
mock_response.json.return_value = {"code": 0, "message": "", "data": True}
|
||||
mock_response = httpx.Response(200, json={"code": 0, "message": "", "data": True})
|
||||
|
||||
with patch("httpx.request", return_value=mock_response, autospec=True):
|
||||
# Act
|
||||
@@ -1002,9 +956,7 @@ class TestPluginInstallerIntegration:
|
||||
def test_fetch_plugin_by_identifier_success(self, installer, mock_config):
|
||||
"""Test successful plugin fetch by identifier."""
|
||||
# Arrange
|
||||
mock_response = MagicMock()
|
||||
mock_response.status_code = 200
|
||||
mock_response.json.return_value = {"code": 0, "message": "", "data": True}
|
||||
mock_response = httpx.Response(200, json={"code": 0, "message": "", "data": True})
|
||||
|
||||
with patch("httpx.request", return_value=mock_response, autospec=True):
|
||||
# Act
|
||||
@@ -1040,9 +992,7 @@ class TestPluginRuntimeEdgeCases:
|
||||
def test_malformed_json_response(self, plugin_client, mock_config):
|
||||
"""Test handling of malformed JSON responses."""
|
||||
# Arrange
|
||||
mock_response = MagicMock()
|
||||
mock_response.status_code = 200
|
||||
mock_response.json.side_effect = json.JSONDecodeError("Invalid JSON", "", 0)
|
||||
mock_response = httpx.Response(200, content=b"invalid JSON")
|
||||
|
||||
with patch("httpx.request", return_value=mock_response, autospec=True):
|
||||
# Act & Assert
|
||||
@@ -1052,10 +1002,8 @@ class TestPluginRuntimeEdgeCases:
|
||||
def test_invalid_response_structure(self, plugin_client, mock_config):
|
||||
"""Test handling of invalid response structure."""
|
||||
# Arrange
|
||||
mock_response = MagicMock()
|
||||
mock_response.status_code = 200
|
||||
# Missing required fields in response
|
||||
mock_response.json.return_value = {"invalid": "structure"}
|
||||
mock_response = httpx.Response(200, json={"invalid": "structure"})
|
||||
|
||||
with patch("httpx.request", return_value=mock_response, autospec=True):
|
||||
# Act & Assert
|
||||
@@ -1070,8 +1018,7 @@ class TestPluginRuntimeEdgeCases:
|
||||
"data: {invalid json}",
|
||||
]
|
||||
|
||||
mock_response = MagicMock()
|
||||
mock_response.iter_lines.return_value = [line.encode("utf-8") for line in stream_data]
|
||||
mock_response = httpx.Response(200, text="\n".join(stream_data))
|
||||
|
||||
with patch("httpx.stream", autospec=True) as mock_stream:
|
||||
mock_stream.return_value.__enter__.return_value = mock_response
|
||||
@@ -1094,8 +1041,7 @@ class TestPluginRuntimeEdgeCases:
|
||||
def test_request_with_bytes_data(self, plugin_client, mock_config):
|
||||
"""Test request with bytes data."""
|
||||
# Arrange
|
||||
mock_response = MagicMock()
|
||||
mock_response.status_code = 200
|
||||
mock_response = httpx.Response(200)
|
||||
|
||||
with patch("httpx.request", return_value=mock_response, autospec=True) as mock_request:
|
||||
# Act
|
||||
@@ -1108,8 +1054,7 @@ class TestPluginRuntimeEdgeCases:
|
||||
def test_request_with_files(self, plugin_client, mock_config):
|
||||
"""Test request with file upload."""
|
||||
# Arrange
|
||||
mock_response = MagicMock()
|
||||
mock_response.status_code = 200
|
||||
mock_response = httpx.Response(200)
|
||||
|
||||
files = {"file": ("test.txt", b"file content", "text/plain")}
|
||||
|
||||
@@ -1124,8 +1069,7 @@ class TestPluginRuntimeEdgeCases:
|
||||
def test_streaming_empty_response(self, plugin_client, mock_config):
|
||||
"""Test streaming with empty response."""
|
||||
# Arrange
|
||||
mock_response = MagicMock()
|
||||
mock_response.iter_lines.return_value = []
|
||||
mock_response = httpx.Response(200, content=b"")
|
||||
|
||||
with patch("httpx.stream", autospec=True) as mock_stream:
|
||||
mock_stream.return_value.__enter__.return_value = mock_response
|
||||
@@ -1144,8 +1088,7 @@ class TestPluginRuntimeEdgeCases:
|
||||
f'data: {{"code": -500, "message": {json.dumps(error_obj)}, "data": null}}',
|
||||
]
|
||||
|
||||
mock_response = MagicMock()
|
||||
mock_response.iter_lines.return_value = [line.encode("utf-8") for line in stream_data]
|
||||
mock_response = httpx.Response(200, text="\n".join(stream_data))
|
||||
|
||||
with patch("httpx.stream", autospec=True) as mock_stream:
|
||||
mock_stream.return_value.__enter__.return_value = mock_response
|
||||
@@ -1164,9 +1107,7 @@ class TestPluginRuntimeEdgeCases:
|
||||
def test_non_json_error_message(self, plugin_client, mock_config):
|
||||
"""Test handling of non-JSON error message."""
|
||||
# Arrange
|
||||
mock_response = MagicMock()
|
||||
mock_response.status_code = 200
|
||||
mock_response.json.return_value = {"code": -1, "message": "Plain text error message", "data": None}
|
||||
mock_response = httpx.Response(200, json={"code": -1, "message": "Plain text error message", "data": None})
|
||||
|
||||
with patch("httpx.request", return_value=mock_response, autospec=True):
|
||||
# Act & Assert
|
||||
@@ -1201,9 +1142,7 @@ class TestPluginRuntimeAdvancedScenarios:
|
||||
def test_multiple_sequential_requests(self, plugin_client, mock_config):
|
||||
"""Test multiple sequential requests to the same endpoint."""
|
||||
# Arrange
|
||||
mock_response = MagicMock()
|
||||
mock_response.status_code = 200
|
||||
mock_response.json.return_value = {"code": 0, "message": "", "data": True}
|
||||
mock_response = httpx.Response(200, json={"code": 0, "message": "", "data": True})
|
||||
|
||||
with patch("httpx.request", return_value=mock_response, autospec=True) as mock_request:
|
||||
# Act
|
||||
@@ -1230,9 +1169,7 @@ class TestPluginRuntimeAdvancedScenarios:
|
||||
],
|
||||
}
|
||||
|
||||
mock_response = MagicMock()
|
||||
mock_response.status_code = 200
|
||||
mock_response.json.return_value = {"code": 0, "message": "", "data": complex_data}
|
||||
mock_response = httpx.Response(200, json={"code": 0, "message": "", "data": complex_data})
|
||||
|
||||
with patch("httpx.request", return_value=mock_response, autospec=True):
|
||||
# Act
|
||||
@@ -1259,8 +1196,7 @@ class TestPluginRuntimeAdvancedScenarios:
|
||||
'{"code": 0, "message": "", "data": {"type": "complete", "data": {"result": "success"}}}',
|
||||
]
|
||||
|
||||
mock_response = MagicMock()
|
||||
mock_response.iter_lines.return_value = [line.encode("utf-8") for line in stream_data]
|
||||
mock_response = httpx.Response(200, text="\n".join(stream_data))
|
||||
|
||||
with patch("httpx.stream", autospec=True) as mock_stream:
|
||||
mock_stream.return_value.__enter__.return_value = mock_response
|
||||
@@ -1289,8 +1225,7 @@ class TestPluginRuntimeAdvancedScenarios:
|
||||
call_count += 1
|
||||
if call_count < 3:
|
||||
raise httpx.RequestError("Temporary failure")
|
||||
mock_response = MagicMock()
|
||||
mock_response.status_code = 200
|
||||
mock_response = httpx.Response(200)
|
||||
return mock_response
|
||||
|
||||
with patch("httpx.request", side_effect=side_effect, autospec=True):
|
||||
@@ -1314,8 +1249,7 @@ class TestPluginRuntimeAdvancedScenarios:
|
||||
"X-Tenant-ID": "tenant-456",
|
||||
}
|
||||
|
||||
mock_response = MagicMock()
|
||||
mock_response.status_code = 200
|
||||
mock_response = httpx.Response(200)
|
||||
|
||||
with patch("httpx.request", return_value=mock_response, autospec=True) as mock_request:
|
||||
# Act
|
||||
@@ -1340,8 +1274,7 @@ class TestPluginRuntimeAdvancedScenarios:
|
||||
f'{{"code": 0, "message": "", "data": {{"chunk_id": {i}, "data": "{large_data}"}}}}' for i in range(10)
|
||||
]
|
||||
|
||||
mock_response = MagicMock()
|
||||
mock_response.iter_lines.return_value = [line.encode("utf-8") for line in stream_data]
|
||||
mock_response = httpx.Response(200, text="\n".join(stream_data))
|
||||
|
||||
with patch("httpx.stream", autospec=True) as mock_stream:
|
||||
mock_stream.return_value.__enter__.return_value = mock_response
|
||||
@@ -1386,8 +1319,7 @@ class TestPluginRuntimeSecurityAndValidation:
|
||||
def test_api_key_header_always_present(self, plugin_client, mock_config):
|
||||
"""Test that API key header is always included in requests."""
|
||||
# Arrange
|
||||
mock_response = MagicMock()
|
||||
mock_response.status_code = 200
|
||||
mock_response = httpx.Response(200)
|
||||
|
||||
with patch("httpx.request", return_value=mock_response, autospec=True) as mock_request:
|
||||
# Act
|
||||
@@ -1407,9 +1339,7 @@ class TestPluginRuntimeSecurityAndValidation:
|
||||
"credentials": {"token": "secret-token"},
|
||||
}
|
||||
|
||||
mock_response = MagicMock()
|
||||
mock_response.status_code = 200
|
||||
mock_response.json.return_value = {"code": 0, "message": "", "data": True}
|
||||
mock_response = httpx.Response(200, json={"code": 0, "message": "", "data": True})
|
||||
|
||||
with patch("httpx.request", return_value=mock_response, autospec=True) as mock_request:
|
||||
# Act
|
||||
@@ -1428,10 +1358,8 @@ class TestPluginRuntimeSecurityAndValidation:
|
||||
def test_unauthorized_access_with_invalid_key(self, plugin_client, mock_config):
|
||||
"""Test handling of unauthorized access with invalid API key."""
|
||||
# Arrange
|
||||
mock_response = MagicMock()
|
||||
mock_response.status_code = 200
|
||||
error_message = json.dumps({"error_type": "PluginDaemonUnauthorizedError", "message": "Invalid API key"})
|
||||
mock_response.json.return_value = {"code": -1, "message": error_message, "data": None}
|
||||
mock_response = httpx.Response(200, json={"code": -1, "message": error_message, "data": None})
|
||||
|
||||
with patch("httpx.request", return_value=mock_response, autospec=True):
|
||||
# Act & Assert
|
||||
@@ -1447,12 +1375,10 @@ class TestPluginRuntimeSecurityAndValidation:
|
||||
"limit": 0, # Invalid zero limit
|
||||
}
|
||||
|
||||
mock_response = MagicMock()
|
||||
mock_response.status_code = 200
|
||||
error_message = json.dumps(
|
||||
{"error_type": "PluginDaemonBadRequestError", "message": "Invalid parameters: page must be positive"}
|
||||
)
|
||||
mock_response.json.return_value = {"code": -1, "message": error_message, "data": None}
|
||||
mock_response = httpx.Response(200, json={"code": -1, "message": error_message, "data": None})
|
||||
|
||||
with patch("httpx.request", return_value=mock_response, autospec=True):
|
||||
# Act & Assert
|
||||
@@ -1465,8 +1391,7 @@ class TestPluginRuntimeSecurityAndValidation:
|
||||
def test_content_type_header_validation(self, plugin_client, mock_config):
|
||||
"""Test that Content-Type header is properly set for JSON requests."""
|
||||
# Arrange
|
||||
mock_response = MagicMock()
|
||||
mock_response.status_code = 200
|
||||
mock_response = httpx.Response(200)
|
||||
|
||||
with patch("httpx.request", return_value=mock_response, autospec=True) as mock_request:
|
||||
# Act
|
||||
@@ -1515,8 +1440,7 @@ class TestPluginRuntimePerformanceScenarios:
|
||||
f'{{"code": 0, "message": "", "data": {{"index": {i}, "value": "chunk_{i}"}}}}' for i in range(100)
|
||||
]
|
||||
|
||||
mock_response = MagicMock()
|
||||
mock_response.iter_lines.return_value = [line.encode("utf-8") for line in stream_data]
|
||||
mock_response = httpx.Response(200, text="\n".join(stream_data))
|
||||
|
||||
with patch("httpx.stream", autospec=True) as mock_stream:
|
||||
mock_stream.return_value.__enter__.return_value = mock_response
|
||||
@@ -1550,8 +1474,7 @@ class TestPluginRuntimePerformanceScenarios:
|
||||
|
||||
stream_data = [f'{{"code": 0, "message": "", "data": {{"data": "chunk_{i}"}}}}' for i in range(10)]
|
||||
|
||||
mock_response = MagicMock()
|
||||
mock_response.iter_lines.return_value = [line.encode("utf-8") for line in stream_data]
|
||||
mock_response = httpx.Response(200, text="\n".join(stream_data))
|
||||
|
||||
with patch("httpx.stream", autospec=True) as mock_stream:
|
||||
mock_stream.return_value.__enter__.return_value = mock_response
|
||||
@@ -1577,9 +1500,7 @@ class TestPluginRuntimePerformanceScenarios:
|
||||
def test_concurrent_request_simulation(self, plugin_client, mock_config):
|
||||
"""Test simulation of concurrent requests (sequential execution in test)."""
|
||||
# Arrange
|
||||
mock_response = MagicMock()
|
||||
mock_response.status_code = 200
|
||||
mock_response.json.return_value = {"code": 0, "message": "", "data": True}
|
||||
mock_response = httpx.Response(200, json={"code": 0, "message": "", "data": True})
|
||||
|
||||
request_results = []
|
||||
|
||||
@@ -1637,8 +1558,7 @@ class TestPluginToolManagerAdvanced:
|
||||
),
|
||||
]
|
||||
|
||||
mock_response = MagicMock()
|
||||
mock_response.iter_lines.return_value = [line.encode("utf-8") for line in stream_data]
|
||||
mock_response = httpx.Response(200, text="\n".join(stream_data))
|
||||
|
||||
with patch("httpx.stream", autospec=True) as mock_stream:
|
||||
mock_stream.return_value.__enter__.return_value = mock_response
|
||||
@@ -1666,8 +1586,7 @@ class TestPluginToolManagerAdvanced:
|
||||
'data: {"code": 0, "message": "", "data": {"type": "text", "message": {"text": "Context-aware result"}}}',
|
||||
]
|
||||
|
||||
mock_response = MagicMock()
|
||||
mock_response.iter_lines.return_value = [line.encode("utf-8") for line in stream_data]
|
||||
mock_response = httpx.Response(200, text="\n".join(stream_data))
|
||||
|
||||
with patch("httpx.stream", autospec=True) as mock_stream:
|
||||
mock_stream.return_value.__enter__.return_value = mock_response
|
||||
@@ -1698,8 +1617,7 @@ class TestPluginToolManagerAdvanced:
|
||||
'data: {"code": 0, "message": "", "data": {"parameters": []}}',
|
||||
]
|
||||
|
||||
mock_response = MagicMock()
|
||||
mock_response.iter_lines.return_value = [line.encode("utf-8") for line in stream_data]
|
||||
mock_response = httpx.Response(200, text="\n".join(stream_data))
|
||||
|
||||
with patch("httpx.stream", autospec=True) as mock_stream:
|
||||
mock_stream.return_value.__enter__.return_value = mock_response
|
||||
@@ -1729,8 +1647,7 @@ class TestPluginToolManagerAdvanced:
|
||||
'data: {"code": 0, "message": "", "data": {"result": true}}',
|
||||
]
|
||||
|
||||
mock_response = MagicMock()
|
||||
mock_response.iter_lines.return_value = [line.encode("utf-8") for line in stream_data]
|
||||
mock_response = httpx.Response(200, text="\n".join(stream_data))
|
||||
|
||||
with patch("httpx.stream", autospec=True) as mock_stream:
|
||||
mock_stream.return_value.__enter__.return_value = mock_response
|
||||
@@ -1774,28 +1691,29 @@ class TestPluginInstallerAdvanced:
|
||||
"""Test successful plugin package upload."""
|
||||
# Arrange
|
||||
plugin_package = b"fake-plugin-package-data"
|
||||
mock_response = MagicMock()
|
||||
mock_response.status_code = 200
|
||||
mock_response.json.return_value = {
|
||||
"code": 0,
|
||||
"message": "",
|
||||
"data": {
|
||||
"unique_identifier": "test-org/test-plugin",
|
||||
"manifest": {
|
||||
"version": "1.0.0",
|
||||
"author": "test-org",
|
||||
"name": "test-plugin",
|
||||
"description": {"en_US": "Test plugin"},
|
||||
"icon": "icon.png",
|
||||
"label": {"en_US": "Test Plugin"},
|
||||
"created_at": "2024-01-01T00:00:00Z",
|
||||
"resource": {"memory": 256},
|
||||
"plugins": {},
|
||||
"meta": {},
|
||||
mock_response = httpx.Response(
|
||||
200,
|
||||
json={
|
||||
"code": 0,
|
||||
"message": "",
|
||||
"data": {
|
||||
"unique_identifier": "test-org/test-plugin",
|
||||
"manifest": {
|
||||
"version": "1.0.0",
|
||||
"author": "test-org",
|
||||
"name": "test-plugin",
|
||||
"description": {"en_US": "Test plugin"},
|
||||
"icon": "icon.png",
|
||||
"label": {"en_US": "Test Plugin"},
|
||||
"created_at": "2024-01-01T00:00:00Z",
|
||||
"resource": {"memory": 256},
|
||||
"plugins": {},
|
||||
"meta": {},
|
||||
},
|
||||
"verification": None,
|
||||
},
|
||||
"verification": None,
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
with patch("httpx.request", return_value=mock_response, autospec=True):
|
||||
# Act
|
||||
@@ -1807,13 +1725,14 @@ class TestPluginInstallerAdvanced:
|
||||
def test_fetch_plugin_readme_success(self, installer, mock_config):
|
||||
"""Test successful plugin readme fetch."""
|
||||
# Arrange
|
||||
mock_response = MagicMock()
|
||||
mock_response.status_code = 200
|
||||
mock_response.json.return_value = {
|
||||
"code": 0,
|
||||
"message": "",
|
||||
"data": {"content": "# Plugin README\n\nThis is a test plugin.", "language": "en"},
|
||||
}
|
||||
mock_response = httpx.Response(
|
||||
200,
|
||||
json={
|
||||
"code": 0,
|
||||
"message": "",
|
||||
"data": {"content": "# Plugin README\n\nThis is a test plugin.", "language": "en"},
|
||||
},
|
||||
)
|
||||
|
||||
with patch("httpx.request", return_value=mock_response, autospec=True):
|
||||
# Act
|
||||
@@ -1826,13 +1745,7 @@ class TestPluginInstallerAdvanced:
|
||||
def test_fetch_plugin_readme_not_found(self, installer, mock_config):
|
||||
"""Test plugin readme fetch when readme doesn't exist."""
|
||||
# Arrange
|
||||
mock_response = MagicMock()
|
||||
mock_response.status_code = 404
|
||||
|
||||
def raise_for_status():
|
||||
raise httpx.HTTPStatusError("Not Found", request=MagicMock(), response=mock_response)
|
||||
|
||||
mock_response.raise_for_status = raise_for_status
|
||||
mock_response = httpx.Response(404)
|
||||
|
||||
with patch("httpx.request", return_value=mock_response, autospec=True):
|
||||
# Act & Assert - Should raise PluginDaemonClientSideError for 404
|
||||
@@ -1842,16 +1755,17 @@ class TestPluginInstallerAdvanced:
|
||||
def test_list_plugins_with_pagination(self, installer, mock_config):
|
||||
"""Test plugin listing with pagination."""
|
||||
# Arrange
|
||||
mock_response = MagicMock()
|
||||
mock_response.status_code = 200
|
||||
mock_response.json.return_value = {
|
||||
"code": 0,
|
||||
"message": "",
|
||||
"data": {
|
||||
"list": [],
|
||||
"total": 50,
|
||||
mock_response = httpx.Response(
|
||||
200,
|
||||
json={
|
||||
"code": 0,
|
||||
"message": "",
|
||||
"data": {
|
||||
"list": [],
|
||||
"total": 50,
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
with patch("httpx.request", return_value=mock_response, autospec=True):
|
||||
# Act
|
||||
@@ -1871,9 +1785,7 @@ class TestPluginInstallerAdvanced:
|
||||
GenericProviderID("langgenius/plugin2/provider2"),
|
||||
]
|
||||
|
||||
mock_response = MagicMock()
|
||||
mock_response.status_code = 200
|
||||
mock_response.json.return_value = {"code": 0, "message": "", "data": [True, False]}
|
||||
mock_response = httpx.Response(200, json={"code": 0, "message": "", "data": [True, False]})
|
||||
|
||||
with patch("httpx.request", return_value=mock_response, autospec=True):
|
||||
# Act
|
||||
|
||||
Reference in new issue
Block a user