Compare commits
3 Commits
main
...
test/real-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
070cd79c39 | ||
|
|
1d912fee41 | ||
|
|
e92ffbdf50 |
@@ -1,17 +1,15 @@
|
||||
from unittest.mock import MagicMock, patch
|
||||
from unittest.mock import patch
|
||||
|
||||
import httpx
|
||||
|
||||
from configs.remote_settings_sources.nacos.http_request import NacosHttpClient
|
||||
|
||||
|
||||
def _ok_response(text: str = "ok", json_data: dict | None = None) -> MagicMock:
|
||||
response = MagicMock()
|
||||
response.text = text
|
||||
response.raise_for_status.return_value = None
|
||||
def _ok_response(text: str = "ok", json_data: dict | None = None) -> httpx.Response:
|
||||
request = httpx.Request("GET", "http://nacos.test")
|
||||
if json_data is not None:
|
||||
response.json.return_value = json_data
|
||||
return response
|
||||
return httpx.Response(200, json=json_data, request=request)
|
||||
return httpx.Response(200, text=text, request=request)
|
||||
|
||||
|
||||
def test_http_request_passes_bounded_timeout():
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
from collections.abc import Callable
|
||||
from types import SimpleNamespace
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
import httpx
|
||||
import pytest
|
||||
from pytest_mock import MockerFixture
|
||||
|
||||
@@ -13,6 +12,11 @@ from core.helper.marketplace import (
|
||||
get_plugin_pkg_url,
|
||||
record_install_plugin_event,
|
||||
)
|
||||
from core.plugin.entities.marketplace import MarketplacePluginSnapshot
|
||||
|
||||
|
||||
def _response(payload: dict[str, object]) -> httpx.Response:
|
||||
return httpx.Response(200, json=payload, request=httpx.Request("POST", "https://marketplace.test"))
|
||||
|
||||
|
||||
def test_get_plugin_pkg_url_contains_unique_identifier() -> None:
|
||||
@@ -45,22 +49,19 @@ def test_batch_fetch_plugin_by_ids_returns_empty_for_empty_input(mocker: MockerF
|
||||
|
||||
|
||||
def test_batch_fetch_plugin_by_ids_returns_plugins_from_response(mocker: MockerFixture) -> None:
|
||||
response = MagicMock()
|
||||
response.json.return_value = {"data": {"plugins": [{"id": "p1"}]}}
|
||||
response.raise_for_status.return_value = None
|
||||
response = _response({"data": {"plugins": [{"id": "p1"}]}})
|
||||
check_status = mocker.spy(response, "raise_for_status")
|
||||
post_mock = mocker.patch("core.helper.marketplace.httpx.post", return_value=response)
|
||||
|
||||
plugins = batch_fetch_plugin_by_ids(["p1"])
|
||||
|
||||
assert plugins == [{"id": "p1"}]
|
||||
post_mock.assert_called_once()
|
||||
response.raise_for_status.assert_called_once()
|
||||
check_status.assert_called_once()
|
||||
|
||||
|
||||
def test_batch_fetch_plugin_by_ids_rejects_invalid_plugins_response(mocker: MockerFixture) -> None:
|
||||
response = MagicMock()
|
||||
response.json.return_value = {"data": {"plugins": ["p1"]}}
|
||||
response.raise_for_status.return_value = None
|
||||
response = _response({"data": {"plugins": ["p1"]}})
|
||||
mocker.patch("core.helper.marketplace.httpx.post", return_value=response)
|
||||
|
||||
with pytest.raises(ValueError, match="plugins list"):
|
||||
@@ -75,9 +76,8 @@ def test_batch_fetch_plugin_manifests_returns_empty_for_empty_input(mocker: Mock
|
||||
|
||||
|
||||
def test_batch_fetch_plugin_manifests_validates_and_returns_plugins(mocker: MockerFixture) -> None:
|
||||
response = MagicMock()
|
||||
response.raise_for_status.return_value = None
|
||||
response.json.return_value = {"data": {"plugins": [{"id": "p1"}, {"id": "p2"}]}}
|
||||
response = _response({"data": {"plugins": [{"id": "p1"}, {"id": "p2"}]}})
|
||||
check_status = mocker.spy(response, "raise_for_status")
|
||||
post_mock = mocker.patch("core.helper.marketplace.httpx.post", return_value=response)
|
||||
validate_mock = mocker.patch(
|
||||
"core.helper.marketplace.MarketplacePluginDeclaration.model_validate",
|
||||
@@ -89,48 +89,47 @@ def test_batch_fetch_plugin_manifests_validates_and_returns_plugins(mocker: Mock
|
||||
assert result == ["manifest-1", "manifest-2"]
|
||||
post_mock.assert_called_once()
|
||||
assert validate_mock.call_count == 2
|
||||
response.raise_for_status.assert_called_once()
|
||||
check_status.assert_called_once()
|
||||
|
||||
|
||||
def test_record_install_plugin_event_posts_and_checks_status(mocker: MockerFixture) -> None:
|
||||
response = MagicMock()
|
||||
response.raise_for_status.return_value = None
|
||||
response = _response({})
|
||||
check_status = mocker.spy(response, "raise_for_status")
|
||||
post_mock = mocker.patch("core.helper.marketplace.httpx.post", return_value=response)
|
||||
|
||||
record_install_plugin_event("plugin.a")
|
||||
|
||||
post_mock.assert_called_once()
|
||||
response.raise_for_status.assert_called_once()
|
||||
check_status.assert_called_once()
|
||||
|
||||
|
||||
def test_fetch_global_plugin_manifest_caches_each_plugin(mocker: MockerFixture) -> None:
|
||||
response = MagicMock()
|
||||
response.raise_for_status.return_value = None
|
||||
response.json.return_value = {"plugins": [{"id": "a"}, {"id": "b"}]}
|
||||
snapshots = [
|
||||
MarketplacePluginSnapshot(
|
||||
org="test",
|
||||
name=name,
|
||||
latest_version="1.0.0",
|
||||
latest_package_identifier=f"test/{name}:1.0.0@checksum",
|
||||
latest_package_url=f"https://marketplace.test/{name}",
|
||||
)
|
||||
for name in ("a", "b")
|
||||
]
|
||||
response = _response({"plugins": [snapshot.model_dump() for snapshot in snapshots]})
|
||||
mocker.patch("core.helper.marketplace.httpx.get", return_value=response)
|
||||
|
||||
snapshot_a = SimpleNamespace(plugin_id="plugin-a", model_dump_json=lambda: '{"id":"a"}')
|
||||
snapshot_b = SimpleNamespace(plugin_id="plugin-b", model_dump_json=lambda: '{"id":"b"}')
|
||||
validate_mock = mocker.patch(
|
||||
"core.helper.marketplace.MarketplacePluginSnapshot.model_validate",
|
||||
side_effect=[snapshot_a, snapshot_b],
|
||||
)
|
||||
setex_mock = mocker.patch("core.helper.marketplace.redis_client.setex")
|
||||
|
||||
cached_count = fetch_global_plugin_manifest("prefix:", 60)
|
||||
|
||||
assert cached_count == 2
|
||||
assert validate_mock.call_count == 2
|
||||
setex_mock.assert_any_call(name="prefix:plugin-a", time=60, value='{"id":"a"}')
|
||||
setex_mock.assert_any_call(name="prefix:plugin-b", time=60, value='{"id":"b"}')
|
||||
for snapshot in snapshots:
|
||||
setex_mock.assert_any_call(name=f"prefix:{snapshot.plugin_id}", time=60, value=snapshot.model_dump_json())
|
||||
|
||||
|
||||
def test_fetch_global_plugin_manifest_reports_zero_for_empty_snapshot(mocker: MockerFixture) -> None:
|
||||
"""An empty snapshot must be reported so callers can fall back instead of upgrading nothing."""
|
||||
empty_plugins: list[dict[str, object]] = []
|
||||
response = MagicMock()
|
||||
response.raise_for_status.return_value = None
|
||||
response.json.return_value = {"metadata": {"plugin_count": 0}, "plugins": empty_plugins}
|
||||
response = _response({"metadata": {"plugin_count": 0}, "plugins": empty_plugins})
|
||||
mocker.patch("core.helper.marketplace.httpx.get", return_value=response)
|
||||
setex_mock = mocker.patch("core.helper.marketplace.redis_client.setex")
|
||||
|
||||
@@ -141,9 +140,7 @@ def test_fetch_global_plugin_manifest_reports_zero_for_empty_snapshot(mocker: Mo
|
||||
|
||||
|
||||
def test_fetch_global_plugin_manifest_reports_zero_when_plugins_key_missing(mocker: MockerFixture) -> None:
|
||||
response = MagicMock()
|
||||
response.raise_for_status.return_value = None
|
||||
response.json.return_value = {"metadata": {"plugin_count": 0}}
|
||||
response = _response({"metadata": {"plugin_count": 0}})
|
||||
mocker.patch("core.helper.marketplace.httpx.get", return_value=response)
|
||||
setex_mock = mocker.patch("core.helper.marketplace.redis_client.setex")
|
||||
|
||||
|
||||
@@ -13,13 +13,7 @@ from core.helper.trace_id_helper import (
|
||||
|
||||
|
||||
class DummyRequest:
|
||||
def __init__(
|
||||
self,
|
||||
headers: dict[str, object] | None = None,
|
||||
args: dict[str, object] | None = None,
|
||||
json: dict[str, object] | None = None,
|
||||
is_json: bool = False,
|
||||
) -> None:
|
||||
def __init__(self, headers=None, args=None, json=None, is_json=False):
|
||||
self.headers = headers or {}
|
||||
self.args = args or {}
|
||||
self.json = json
|
||||
@@ -27,21 +21,14 @@ class DummyRequest:
|
||||
|
||||
|
||||
class _Request:
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
headers: dict[str, object] | None = None,
|
||||
args: dict[str, object] | None = None,
|
||||
json: dict[str, object] | None = None,
|
||||
is_json: bool = True,
|
||||
) -> None:
|
||||
def __init__(self, *, headers=None, args=None, json=None, is_json=True):
|
||||
self.headers = headers or {}
|
||||
self.args = args or {}
|
||||
self.json = json
|
||||
self.is_json = is_json
|
||||
|
||||
|
||||
def test_get_trace_session_id_prefers_header_over_query_and_body() -> None:
|
||||
def test_get_trace_session_id_prefers_header_over_query_and_body():
|
||||
request = _Request(
|
||||
headers={"X-Trace-Session-Id": " header-session "},
|
||||
args={"trace_session_id": "query-session"},
|
||||
@@ -51,7 +38,7 @@ def test_get_trace_session_id_prefers_header_over_query_and_body() -> None:
|
||||
assert get_trace_session_id(request) == "header-session"
|
||||
|
||||
|
||||
def test_get_trace_session_id_prefers_query_over_body() -> None:
|
||||
def test_get_trace_session_id_prefers_query_over_body():
|
||||
request = _Request(
|
||||
args={"trace_session_id": " query-session "},
|
||||
json={"trace_session_id": "body-session"},
|
||||
@@ -60,13 +47,13 @@ def test_get_trace_session_id_prefers_query_over_body() -> None:
|
||||
assert get_trace_session_id(request) == "query-session"
|
||||
|
||||
|
||||
def test_get_trace_session_id_reads_body_when_no_higher_priority_input() -> None:
|
||||
def test_get_trace_session_id_reads_body_when_no_higher_priority_input():
|
||||
request = _Request(json={"trace_session_id": " body/session:123 "})
|
||||
|
||||
assert get_trace_session_id(request) == "body/session:123"
|
||||
|
||||
|
||||
def test_get_trace_session_id_ignores_invalid_lower_priority_value() -> None:
|
||||
def test_get_trace_session_id_ignores_invalid_lower_priority_value():
|
||||
request = _Request(
|
||||
headers={"X-Trace-Session-Id": "header-session"},
|
||||
json={"trace_session_id": " "},
|
||||
@@ -83,14 +70,14 @@ def test_get_trace_session_id_ignores_invalid_lower_priority_value() -> None:
|
||||
_Request(headers={"X-Trace-Session-Id": "x" * 201}),
|
||||
],
|
||||
)
|
||||
def test_get_trace_session_id_rejects_invalid_highest_priority_input(trace_session_request: _Request) -> None:
|
||||
def test_get_trace_session_id_rejects_invalid_highest_priority_input(trace_session_request):
|
||||
with pytest.raises(BadRequest) as exc_info:
|
||||
get_trace_session_id(trace_session_request)
|
||||
|
||||
assert "trace_session_id" in str(exc_info.value)
|
||||
|
||||
|
||||
def test_get_trace_session_id_does_not_read_trace_id_or_traceparent() -> None:
|
||||
def test_get_trace_session_id_does_not_read_trace_id_or_traceparent():
|
||||
request = _Request(
|
||||
headers={
|
||||
"X-Trace-Id": "trace-id",
|
||||
@@ -103,17 +90,17 @@ def test_get_trace_session_id_does_not_read_trace_id_or_traceparent() -> None:
|
||||
assert get_trace_session_id(request) is None
|
||||
|
||||
|
||||
def test_extract_trace_session_id_from_args_returns_trimmed_value() -> None:
|
||||
def test_extract_trace_session_id_from_args_returns_trimmed_value():
|
||||
args = {"trace_session_id": " session-1 "}
|
||||
|
||||
assert extract_trace_session_id_from_args(args) == {"trace_session_id": "session-1"}
|
||||
|
||||
|
||||
def test_extract_trace_session_id_from_args_returns_empty_dict_when_missing() -> None:
|
||||
def test_extract_trace_session_id_from_args_returns_empty_dict_when_missing():
|
||||
assert extract_trace_session_id_from_args({}) == {}
|
||||
|
||||
|
||||
def test_extract_trace_session_id_from_args_returns_empty_dict_when_blank_after_trim() -> None:
|
||||
def test_extract_trace_session_id_from_args_returns_empty_dict_when_blank_after_trim():
|
||||
assert extract_trace_session_id_from_args({"trace_session_id": " "}) == {}
|
||||
|
||||
|
||||
@@ -133,26 +120,26 @@ class TestTraceIdHelper:
|
||||
("with space", False),
|
||||
],
|
||||
)
|
||||
def test_is_valid_trace_id(self, trace_id: str, expected: bool) -> None:
|
||||
def test_is_valid_trace_id(self, trace_id, expected):
|
||||
"""Test trace_id validation for various cases"""
|
||||
assert is_valid_trace_id(trace_id) is expected
|
||||
|
||||
def test_get_external_trace_id_from_header(self) -> None:
|
||||
def test_get_external_trace_id_from_header(self):
|
||||
"""Should extract valid trace_id from header"""
|
||||
req = DummyRequest(headers={"X-Trace-Id": "abc123"})
|
||||
assert get_external_trace_id(req) == "abc123"
|
||||
|
||||
def test_get_external_trace_id_from_args(self) -> None:
|
||||
def test_get_external_trace_id_from_args(self):
|
||||
"""Should extract valid trace_id from args if header missing"""
|
||||
req = DummyRequest(args={"trace_id": "abc123"})
|
||||
assert get_external_trace_id(req) == "abc123"
|
||||
|
||||
def test_get_external_trace_id_from_json(self) -> None:
|
||||
def test_get_external_trace_id_from_json(self):
|
||||
"""Should extract valid trace_id from JSON body if header and args missing"""
|
||||
req = DummyRequest(is_json=True, json={"trace_id": "abc123"})
|
||||
assert get_external_trace_id(req) == "abc123"
|
||||
|
||||
def test_get_external_trace_id_priority(self) -> None:
|
||||
def test_get_external_trace_id_priority(self):
|
||||
"""Header > args > json priority"""
|
||||
req = DummyRequest(
|
||||
headers={"X-Trace-Id": "header_id"},
|
||||
@@ -175,7 +162,7 @@ class TestTraceIdHelper:
|
||||
DummyRequest(),
|
||||
],
|
||||
)
|
||||
def test_get_external_trace_id_invalid(self, req: DummyRequest) -> None:
|
||||
def test_get_external_trace_id_invalid(self, req):
|
||||
"""Should return None for invalid or missing trace_id"""
|
||||
assert get_external_trace_id(req) is None
|
||||
|
||||
@@ -187,7 +174,7 @@ class TestTraceIdHelper:
|
||||
({}, {}),
|
||||
],
|
||||
)
|
||||
def test_extract_external_trace_id_from_args(self, args: dict[str, object], expected: dict[str, object]) -> None:
|
||||
def test_extract_external_trace_id_from_args(self, args, expected):
|
||||
"""Test extraction of external_trace_id from args mapping"""
|
||||
assert extract_external_trace_id_from_args(args) == expected
|
||||
|
||||
@@ -245,11 +232,11 @@ class TestTraceIdHelper:
|
||||
({}, {}),
|
||||
],
|
||||
)
|
||||
def test_extract_parent_trace_context_from_args(self, args: dict[str, object], expected: dict[str, object]) -> None:
|
||||
def test_extract_parent_trace_context_from_args(self, args, expected):
|
||||
"""Test extraction of parent_trace_context from args mapping"""
|
||||
assert extract_parent_trace_context_from_args(args) == expected
|
||||
|
||||
def test_extract_parent_trace_context_returns_typed_context(self) -> None:
|
||||
def test_extract_parent_trace_context_returns_typed_context(self):
|
||||
"""Parent trace context is parsed into a Pydantic value object."""
|
||||
result = extract_parent_trace_context_from_args(
|
||||
{
|
||||
@@ -267,7 +254,7 @@ class TestTraceIdHelper:
|
||||
)
|
||||
}
|
||||
|
||||
def test_extract_parent_trace_context_rejects_incomplete_typed_context(self) -> None:
|
||||
def test_extract_parent_trace_context_rejects_incomplete_typed_context(self):
|
||||
"""Typed parent trace context follows the same completeness rule as raw mappings."""
|
||||
result = extract_parent_trace_context_from_args(
|
||||
{
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
import httpx
|
||||
import pytest
|
||||
from pytest_mock import MockerFixture
|
||||
|
||||
@@ -9,7 +8,7 @@ from core.plugin.impl.asset import PluginAssetManager
|
||||
class TestPluginAssetManager:
|
||||
def test_fetch_asset_success(self, mocker: MockerFixture):
|
||||
manager = PluginAssetManager()
|
||||
response = MagicMock(status_code=200, content=b"asset-bytes")
|
||||
response = httpx.Response(status_code=200, content=b"asset-bytes")
|
||||
request_mock = mocker.patch.object(manager, "_request", return_value=response)
|
||||
|
||||
result = manager.fetch_asset("tenant-1", "asset-1")
|
||||
@@ -19,14 +18,14 @@ class TestPluginAssetManager:
|
||||
|
||||
def test_fetch_asset_not_found_raises(self, mocker: MockerFixture):
|
||||
manager = PluginAssetManager()
|
||||
mocker.patch.object(manager, "_request", return_value=MagicMock(status_code=404, content=b""))
|
||||
mocker.patch.object(manager, "_request", return_value=httpx.Response(status_code=404, content=b""))
|
||||
|
||||
with pytest.raises(ValueError, match="can not found asset asset-1"):
|
||||
manager.fetch_asset("tenant-1", "asset-1")
|
||||
|
||||
def test_extract_asset_success(self, mocker: MockerFixture):
|
||||
manager = PluginAssetManager()
|
||||
response = MagicMock(status_code=200, content=b"file-content")
|
||||
response = httpx.Response(status_code=200, content=b"file-content")
|
||||
request_mock = mocker.patch.object(manager, "_request", return_value=response)
|
||||
|
||||
result = manager.extract_asset("tenant-1", "org/plugin:1", "README.md")
|
||||
@@ -40,7 +39,7 @@ class TestPluginAssetManager:
|
||||
|
||||
def test_extract_asset_not_found_raises(self, mocker: MockerFixture):
|
||||
manager = PluginAssetManager()
|
||||
mocker.patch.object(manager, "_request", return_value=MagicMock(status_code=404, content=b""))
|
||||
mocker.patch.object(manager, "_request", return_value=httpx.Response(status_code=404, content=b""))
|
||||
|
||||
with pytest.raises(ValueError, match="can not found asset org/plugin:1, 404"):
|
||||
manager.extract_asset("tenant-1", "org/plugin:1", "README.md")
|
||||
|
||||
@@ -1,14 +1,17 @@
|
||||
from unittest.mock import Mock, patch
|
||||
from unittest.mock import patch
|
||||
|
||||
from core.plugin.impl.model_runtime_factory import create_plugin_model_assembly
|
||||
from core.model_manager import ModelManager
|
||||
from core.plugin.impl.model_runtime_factory import create_plugin_model_assembly, create_plugin_model_runtime
|
||||
from core.plugin.plugin_service import PluginService
|
||||
from core.provider_manager import ProviderManager
|
||||
from graphon.model_runtime.model_providers.model_provider_factory import ModelProviderFactory
|
||||
|
||||
|
||||
def test_plugin_model_assembly_reuses_single_runtime_across_views():
|
||||
runtime = Mock(name="runtime")
|
||||
provider_factory = Mock(name="provider_factory")
|
||||
provider_manager = Mock(name="provider_manager")
|
||||
model_manager = Mock(name="model_manager")
|
||||
runtime = create_plugin_model_runtime(tenant_id="tenant-1", user_id="user-1")
|
||||
provider_factory = ModelProviderFactory(runtime=runtime)
|
||||
provider_manager = ProviderManager(model_runtime=runtime)
|
||||
model_manager = ModelManager(provider_manager=provider_manager)
|
||||
|
||||
with (
|
||||
patch(
|
||||
@@ -38,8 +41,6 @@ def test_plugin_model_assembly_reuses_single_runtime_across_views():
|
||||
|
||||
|
||||
def test_create_plugin_model_runtime_injects_plugin_service():
|
||||
from core.plugin.impl.model_runtime_factory import create_plugin_model_runtime
|
||||
|
||||
runtime = create_plugin_model_runtime(tenant_id="tenant-1", user_id="user-1")
|
||||
|
||||
assert runtime._plugin_service is PluginService
|
||||
|
||||
@@ -370,6 +370,7 @@ project-excludes = [
|
||||
"core/helper/test_credential_utils.py",
|
||||
"core/helper/test_encrypter.py",
|
||||
"core/helper/test_ssrf_proxy.py",
|
||||
"core/helper/test_trace_id_helper.py",
|
||||
"core/llm_generator/output_parser/test_rule_config_generator.py",
|
||||
"core/llm_generator/output_parser/test_structured_output.py",
|
||||
"core/llm_generator/test_llm_generator.py",
|
||||
|
||||
Reference in New Issue
Block a user