mirror of
https://github.com/willmiao/ComfyUI-Lora-Manager.git
synced 2026-09-21 03:01:27 -03:00
feat(backend): add Other model type (VAE/upscaler/text encoder) scanner, service and routes
This commit is contained in:
@@ -132,6 +132,7 @@ async def test_lora_manager_lifecycle(monkeypatch: pytest.MonkeyPatch, tmp_path:
|
||||
"lora": _DummyScanner("lora"),
|
||||
"checkpoint": _DummyScanner("checkpoint"),
|
||||
"embedding": _DummyScanner("embedding"),
|
||||
"other": _DummyScanner("other"),
|
||||
"recipe": _DummyScanner("recipe"),
|
||||
}
|
||||
|
||||
@@ -147,6 +148,7 @@ async def test_lora_manager_lifecycle(monkeypatch: pytest.MonkeyPatch, tmp_path:
|
||||
monkeypatch.setattr(lora_manager.ServiceRegistry, "get_lora_scanner", lambda: _stub("lora_scanner", scanners["lora"]))
|
||||
monkeypatch.setattr(lora_manager.ServiceRegistry, "get_checkpoint_scanner", lambda: _stub("checkpoint_scanner", scanners["checkpoint"]))
|
||||
monkeypatch.setattr(lora_manager.ServiceRegistry, "get_embedding_scanner", lambda: _stub("embedding_scanner", scanners["embedding"]))
|
||||
monkeypatch.setattr(lora_manager.ServiceRegistry, "get_other_scanner", lambda: _stub("other_scanner", scanners["other"]))
|
||||
monkeypatch.setattr(lora_manager.ServiceRegistry, "get_recipe_scanner", lambda: _stub("recipe_scanner", scanners["recipe"]))
|
||||
|
||||
migration_calls: list[bool] = []
|
||||
@@ -205,7 +207,7 @@ async def test_lora_manager_lifecycle(monkeypatch: pytest.MonkeyPatch, tmp_path:
|
||||
await asyncio.gather(*pending)
|
||||
|
||||
task_names = {task.get_name() for task in scheduled_tasks}
|
||||
assert {"lora_cache_init", "checkpoint_cache_init", "embedding_cache_init", "recipe_cache_init", "post_init_tasks", "cleanup_bak_files"}.issubset(task_names)
|
||||
assert {"lora_cache_init", "checkpoint_cache_init", "embedding_cache_init", "other_cache_init", "recipe_cache_init", "post_init_tasks", "cleanup_bak_files"}.issubset(task_names)
|
||||
|
||||
# Startup sweep: an expired pending-delete purge task is spawned during
|
||||
# service initialization (covers both plugin and standalone modes).
|
||||
@@ -219,4 +221,4 @@ async def test_lora_manager_lifecycle(monkeypatch: pytest.MonkeyPatch, tmp_path:
|
||||
for root in (loras_root, checkpoints_root, embeddings_root):
|
||||
assert not any(path.suffix == ".bak" for path in root.rglob("*")), f"Backup files remain in {root}"
|
||||
|
||||
assert {"civitai_client", "download_manager", "websocket_manager", "lora_scanner", "checkpoint_scanner", "embedding_scanner", "recipe_scanner"}.issubset(registry_calls)
|
||||
assert {"civitai_client", "download_manager", "websocket_manager", "lora_scanner", "checkpoint_scanner", "embedding_scanner", "other_scanner", "recipe_scanner"}.issubset(registry_calls)
|
||||
|
||||
@@ -0,0 +1,118 @@
|
||||
import json
|
||||
|
||||
import pytest
|
||||
from aiohttp import web
|
||||
|
||||
from py.routes.other_routes import OtherRoutes
|
||||
from py.services.other_model_service import OtherModelService
|
||||
|
||||
|
||||
class DummyRequest:
|
||||
def __init__(self, *, match_info=None):
|
||||
self.match_info = match_info or {}
|
||||
|
||||
|
||||
class StubOtherModelService:
|
||||
def __init__(self):
|
||||
self.info = {}
|
||||
|
||||
async def get_model_info_by_name(self, name):
|
||||
value = self.info.get(name)
|
||||
if isinstance(value, Exception):
|
||||
raise value
|
||||
return value
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def routes():
|
||||
handler = OtherRoutes()
|
||||
handler.service = StubOtherModelService() # pyright: ignore[reportAttributeAccessIssue]
|
||||
return handler
|
||||
|
||||
|
||||
def test_common_and_specific_routes_registered():
|
||||
"""Registration smoke test: /api/lm/other/* surface plus the /other page."""
|
||||
app = web.Application()
|
||||
OtherRoutes().setup_routes(app)
|
||||
|
||||
registered = {(route.method, route.resource.canonical) for route in app.router.routes()}
|
||||
|
||||
assert ("GET", "/other") in registered
|
||||
assert ("GET", "/api/lm/other/list") in registered
|
||||
assert ("GET", "/api/lm/other/model-types") in registered
|
||||
assert ("GET", "/api/lm/other/roots") in registered
|
||||
assert ("POST", "/api/lm/other/fetch-civitai") in registered
|
||||
assert ("POST", "/api/lm/other/delete") in registered
|
||||
assert ("POST", "/api/lm/other/move_model") in registered
|
||||
assert ("GET", "/api/lm/other/info/{name}") in registered
|
||||
|
||||
|
||||
def test_template_name_is_other_page():
|
||||
assert OtherRoutes().template_name == "other.html"
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"model_type",
|
||||
["VAE", "Upscaler", "TextEncoder", "CLIP", "CLIPVision", "Controlnet", "Other"],
|
||||
)
|
||||
def test_validate_civitai_model_type_accepts_other_types(model_type):
|
||||
assert OtherRoutes()._validate_civitai_model_type(model_type) is True
|
||||
|
||||
|
||||
@pytest.mark.parametrize("model_type", ["Lora", "Checkpoint", "TextualInversion"])
|
||||
def test_validate_civitai_model_type_rejects_foreign_types(model_type):
|
||||
assert OtherRoutes()._validate_civitai_model_type(model_type) is False
|
||||
|
||||
|
||||
def test_get_expected_model_types_mentions_supported_types():
|
||||
expected = OtherRoutes()._get_expected_model_types()
|
||||
for name in ("VAE", "Upscaler", "TextEncoder", "CLIPVision", "Controlnet"):
|
||||
assert name in expected
|
||||
|
||||
|
||||
async def test_get_other_model_info_success(routes):
|
||||
routes.service.info["demo"] = {"name": "demo"}
|
||||
response = await routes.get_other_model_info(DummyRequest(match_info={"name": "demo"}))
|
||||
payload = json.loads(response.text)
|
||||
assert payload == {"name": "demo"}
|
||||
|
||||
|
||||
async def test_get_other_model_info_missing(routes):
|
||||
response = await routes.get_other_model_info(DummyRequest(match_info={"name": "missing"}))
|
||||
payload = json.loads(response.text)
|
||||
assert response.status == 404
|
||||
assert payload == {"error": "Model not found"}
|
||||
|
||||
|
||||
async def test_get_other_model_info_error(routes):
|
||||
routes.service.info["demo"] = RuntimeError("boom")
|
||||
response = await routes.get_other_model_info(DummyRequest(match_info={"name": "demo"}))
|
||||
payload = json.loads(response.text)
|
||||
assert response.status == 500
|
||||
assert payload == {"error": "boom"}
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_initialize_services_builds_other_model_service(monkeypatch):
|
||||
from py.services.service_registry import ServiceRegistry
|
||||
|
||||
sentinel_scanner = object()
|
||||
sentinel_update_service = object()
|
||||
|
||||
async def fake_scanner():
|
||||
return sentinel_scanner
|
||||
|
||||
async def fake_update_service():
|
||||
return sentinel_update_service
|
||||
|
||||
monkeypatch.setattr(ServiceRegistry, "get_other_scanner", staticmethod(fake_scanner))
|
||||
monkeypatch.setattr(
|
||||
ServiceRegistry, "get_model_update_service", staticmethod(fake_update_service)
|
||||
)
|
||||
|
||||
handler = OtherRoutes()
|
||||
await handler.initialize_services()
|
||||
|
||||
assert isinstance(handler.service, OtherModelService)
|
||||
assert handler.service.model_type == "other"
|
||||
assert handler.service.scanner is sentinel_scanner
|
||||
Reference in New Issue
Block a user