mirror of
https://github.com/willmiao/ComfyUI-Lora-Manager.git
synced 2026-03-22 05:32:12 -03:00
Addressed the aiohttp warnings by aligning the test scaffolding with current best practices. Added an AppKey constant and stored the route tracking list through it to satisfy aiohttp’s NotAppKeyWarning expectations. Swapped the websocket lambdas for async no-op handlers so the registered routes now point to coroutine callables, clearing the deprecation warning about bare functions.
222 lines
7.1 KiB
Python
222 lines
7.1 KiB
Python
"""Tests covering the standalone bootstrap flow."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
from pathlib import Path
|
|
from types import ModuleType, SimpleNamespace
|
|
from typing import List, Tuple
|
|
|
|
import pytest
|
|
from aiohttp import web
|
|
|
|
from py.utils.settings_paths import ensure_settings_file
|
|
|
|
|
|
ROUTE_CALLS_KEY: web.AppKey[List[Tuple[str, dict]]] = web.AppKey("route_calls")
|
|
|
|
|
|
@pytest.fixture
|
|
def standalone_module(monkeypatch) -> ModuleType:
|
|
"""Load the ``standalone`` module with a lightweight ``LoraManager`` stub."""
|
|
|
|
import importlib
|
|
import sys
|
|
from types import ModuleType
|
|
|
|
original_lora_manager = sys.modules.get("py.lora_manager")
|
|
|
|
stub_module = ModuleType("py.lora_manager")
|
|
|
|
class _StubLoraManager:
|
|
@classmethod
|
|
def add_routes(cls): # pragma: no cover - compatibility shim
|
|
return None
|
|
|
|
@classmethod
|
|
async def _initialize_services(cls): # pragma: no cover - compatibility shim
|
|
return None
|
|
|
|
@classmethod
|
|
async def _cleanup(cls, app): # pragma: no cover - compatibility shim
|
|
return None
|
|
|
|
stub_module.LoraManager = _StubLoraManager
|
|
sys.modules["py.lora_manager"] = stub_module
|
|
|
|
module = importlib.import_module("standalone")
|
|
yield module
|
|
|
|
sys.modules.pop("standalone", None)
|
|
if original_lora_manager is not None:
|
|
sys.modules["py.lora_manager"] = original_lora_manager
|
|
else:
|
|
sys.modules.pop("py.lora_manager", None)
|
|
|
|
|
|
def _write_settings(contents: dict) -> Path:
|
|
"""Persist *contents* into the isolated settings.json."""
|
|
|
|
settings_path = Path(ensure_settings_file())
|
|
settings_path.write_text(json.dumps(contents))
|
|
return settings_path
|
|
|
|
|
|
async def test_standalone_server_sets_up_routes(tmp_path, standalone_module):
|
|
"""``StandaloneServer.setup`` wires the HTTP routes and lifecycle hooks."""
|
|
|
|
example_images_dir = tmp_path / "example_images"
|
|
example_images_dir.mkdir()
|
|
(example_images_dir / "preview.png").write_text("placeholder")
|
|
|
|
_write_settings({"example_images_path": str(example_images_dir)})
|
|
|
|
server = standalone_module.StandaloneServer()
|
|
await server.setup()
|
|
|
|
canonical_routes = {resource.canonical for resource in server.app.router.resources()}
|
|
|
|
assert "/" in canonical_routes, "status endpoint should be registered"
|
|
assert (
|
|
"/example_images_static" in canonical_routes
|
|
), "static example image route should be exposed when the directory exists"
|
|
assert server.app.on_startup, "startup callbacks must be attached"
|
|
assert server.app.on_shutdown, "shutdown callbacks must be attached"
|
|
|
|
|
|
def test_validate_settings_warns_for_missing_model_paths(caplog, standalone_module):
|
|
"""Missing model folders trigger the configuration warning."""
|
|
|
|
caplog.set_level("WARNING")
|
|
|
|
_write_settings(
|
|
{
|
|
"folder_paths": {
|
|
"loras": ["/non/existent"],
|
|
"checkpoints": [],
|
|
"embeddings": [],
|
|
}
|
|
}
|
|
)
|
|
|
|
assert standalone_module.validate_settings() is False
|
|
warning_lines = [record.message for record in caplog.records if record.levelname == "WARNING"]
|
|
assert any("CONFIGURATION WARNING" in line for line in warning_lines)
|
|
|
|
|
|
def test_standalone_lora_manager_registers_routes(monkeypatch, tmp_path, standalone_module):
|
|
"""``StandaloneLoraManager.add_routes`` registers static and websocket routes."""
|
|
|
|
app = web.Application()
|
|
route_calls: List[Tuple[str, dict]] = []
|
|
app[ROUTE_CALLS_KEY] = route_calls
|
|
|
|
locales_dir = tmp_path / "locales"
|
|
locales_dir.mkdir()
|
|
static_dir = tmp_path / "static"
|
|
static_dir.mkdir()
|
|
|
|
monkeypatch.setattr(
|
|
standalone_module,
|
|
"config",
|
|
SimpleNamespace(i18n_path=str(locales_dir), static_path=str(static_dir)),
|
|
)
|
|
|
|
register_calls: List[str] = []
|
|
|
|
import py.services.model_service_factory as factory_module
|
|
|
|
def fake_register_default_model_types() -> None:
|
|
register_calls.append("called")
|
|
|
|
monkeypatch.setattr(
|
|
factory_module,
|
|
"register_default_model_types",
|
|
fake_register_default_model_types,
|
|
)
|
|
|
|
def fake_setup_all_routes(cls, app_arg):
|
|
route_calls.append(("ModelServiceFactory.setup_all_routes", {"app": app_arg}))
|
|
|
|
monkeypatch.setattr(
|
|
factory_module.ModelServiceFactory,
|
|
"setup_all_routes",
|
|
classmethod(fake_setup_all_routes),
|
|
)
|
|
|
|
class DummyRecipeRoutes:
|
|
@staticmethod
|
|
def setup_routes(app_arg):
|
|
route_calls.append(("RecipeRoutes", {}))
|
|
|
|
class DummyUpdateRoutes:
|
|
@staticmethod
|
|
def setup_routes(app_arg):
|
|
route_calls.append(("UpdateRoutes", {}))
|
|
|
|
class DummyMiscRoutes:
|
|
@staticmethod
|
|
def setup_routes(app_arg):
|
|
route_calls.append(("MiscRoutes", {}))
|
|
|
|
class DummyExampleImagesRoutes:
|
|
@staticmethod
|
|
def setup_routes(app_arg, **kwargs):
|
|
route_calls.append(("ExampleImagesRoutes", kwargs))
|
|
|
|
class DummyPreviewRoutes:
|
|
@staticmethod
|
|
def setup_routes(app_arg):
|
|
route_calls.append(("PreviewRoutes", {}))
|
|
|
|
class DummyStatsRoutes:
|
|
def setup_routes(self, app_arg):
|
|
route_calls.append(("StatsRoutes", {}))
|
|
|
|
monkeypatch.setattr("py.routes.recipe_routes.RecipeRoutes", DummyRecipeRoutes)
|
|
monkeypatch.setattr("py.routes.update_routes.UpdateRoutes", DummyUpdateRoutes)
|
|
monkeypatch.setattr("py.routes.misc_routes.MiscRoutes", DummyMiscRoutes)
|
|
monkeypatch.setattr(
|
|
"py.routes.example_images_routes.ExampleImagesRoutes",
|
|
DummyExampleImagesRoutes,
|
|
)
|
|
monkeypatch.setattr("py.routes.preview_routes.PreviewRoutes", DummyPreviewRoutes)
|
|
monkeypatch.setattr("py.routes.stats_routes.StatsRoutes", DummyStatsRoutes)
|
|
|
|
async def _noop_ws_handler(request):
|
|
return web.Response(status=204)
|
|
|
|
ws_manager_stub = SimpleNamespace(
|
|
handle_connection=_noop_ws_handler,
|
|
handle_download_connection=_noop_ws_handler,
|
|
handle_init_connection=_noop_ws_handler,
|
|
)
|
|
monkeypatch.setattr("py.services.websocket_manager.ws_manager", ws_manager_stub)
|
|
|
|
server = SimpleNamespace(app=app)
|
|
|
|
standalone_module.StandaloneLoraManager.add_routes(server)
|
|
|
|
assert register_calls, "default model types should be registered"
|
|
|
|
canonical_routes = {resource.canonical for resource in app.router.resources()}
|
|
assert "/locales" in canonical_routes
|
|
assert "/loras_static" in canonical_routes
|
|
|
|
websocket_paths = {route.resource.canonical for route in app.router.routes() if "ws" in route.resource.canonical}
|
|
assert {
|
|
"/ws/fetch-progress",
|
|
"/ws/download-progress",
|
|
"/ws/init-progress",
|
|
} <= websocket_paths
|
|
|
|
assert any(call[0] == "ModelServiceFactory.setup_all_routes" for call in route_calls)
|
|
assert any(call[0] == "RecipeRoutes" for call in route_calls)
|
|
assert any(call[0] == "StatsRoutes" for call in route_calls)
|
|
|
|
prompt_server = pytest.importorskip("server").PromptServer
|
|
assert getattr(prompt_server, "instance", None) is server
|
|
|
|
assert app.on_startup, "service initialization hook should be scheduled"
|
|
assert app.on_shutdown, "cleanup hook should be scheduled"
|