Compare commits

...

3 Commits

Author SHA1 Message Date
Asuka Minato
070cd79c39 test: use real plugin runtime objects 2026-10-07 00:10:53 +09:00
Codex
1d912fee41 test: specify marketplace response payload types 2026-10-07 00:07:16 +09:00
Asuka Minato
e92ffbdf50 test: use real marketplace and Nacos responses 2026-10-07 00:07:16 +09:00
4 changed files with 50 additions and 55 deletions

View File

@@ -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():

View File

@@ -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")

View File

@@ -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")

View File

@@ -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