mirror of
https://github.com/willmiao/ComfyUI-Lora-Manager.git
synced 2026-03-21 21:22:11 -03:00
feat(testing): implement Phase 4 advanced testing
- Add Hypothesis property-based tests (19 tests) - Add Syrupy snapshot tests (7 tests) - Add pytest-benchmark performance tests (11 tests) - Fix Hypothesis plugin compatibility by creating MockModule class - Update pytest.ini to exclude .hypothesis directory - Add .hypothesis/ to .gitignore - Update requirements-dev.txt with testing dependencies - Mark Phase 4 complete in backend-testing-improvement-plan.md All 947 tests passing.
This commit is contained in:
67
tests/routes/__snapshots__/test_api_snapshots.ambr
Normal file
67
tests/routes/__snapshots__/test_api_snapshots.ambr
Normal file
@@ -0,0 +1,67 @@
|
||||
# serializer version: 1
|
||||
# name: TestModelLibraryHandlerSnapshots.test_check_model_exists_empty_response
|
||||
dict({
|
||||
'modelType': None,
|
||||
'success': True,
|
||||
'versions': list([
|
||||
]),
|
||||
})
|
||||
# ---
|
||||
# name: TestNodeRegistryHandlerSnapshots.test_register_nodes_error_response
|
||||
dict({
|
||||
'message': '0 nodes registered successfully',
|
||||
'success': True,
|
||||
})
|
||||
# ---
|
||||
# name: TestNodeRegistryHandlerSnapshots.test_register_nodes_success_response
|
||||
dict({
|
||||
'message': '1 nodes registered successfully',
|
||||
'success': True,
|
||||
})
|
||||
# ---
|
||||
# name: TestSettingsHandlerSnapshots.test_get_settings_response_format
|
||||
dict({
|
||||
'messages': list([
|
||||
]),
|
||||
'settings': dict({
|
||||
'civitai_api_key': 'test-key',
|
||||
'language': 'en',
|
||||
}),
|
||||
'success': True,
|
||||
})
|
||||
# ---
|
||||
# name: TestSettingsHandlerSnapshots.test_update_settings_success_response
|
||||
dict({
|
||||
'success': True,
|
||||
})
|
||||
# ---
|
||||
# name: TestUtilityFunctionSnapshots.test_calculate_recipe_fingerprint_various_inputs
|
||||
list([
|
||||
'',
|
||||
'abc123:1.0',
|
||||
'abc123:1.0|def456:0.75',
|
||||
'abc123:0.5|def456:1.0',
|
||||
'abc123:0.8',
|
||||
'12345:1.0',
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
])
|
||||
# ---
|
||||
# name: TestUtilityFunctionSnapshots.test_sanitize_folder_name_various_inputs
|
||||
dict({
|
||||
'': '',
|
||||
' spaces ': 'spaces',
|
||||
'___underscores___': 'underscores',
|
||||
'folder with spaces': 'folder with spaces',
|
||||
'folder"with"quotes': 'folder_with_quotes',
|
||||
'folder*with*asterisks': 'folder_with_asterisks',
|
||||
'folder.with.dots': 'folder.with.dots',
|
||||
'folder/with/slashes': 'folder_with_slashes',
|
||||
'folder<with>brackets': 'folder_with_brackets',
|
||||
'folder?with?questions': 'folder_with_questions',
|
||||
'folder\\with\\backslashes': 'folder_with_backslashes',
|
||||
'folder|with|pipes': 'folder_with_pipes',
|
||||
'normal_folder': 'normal_folder',
|
||||
})
|
||||
# ---
|
||||
230
tests/routes/test_api_snapshots.py
Normal file
230
tests/routes/test_api_snapshots.py
Normal file
@@ -0,0 +1,230 @@
|
||||
"""Snapshot tests for API response formats using Syrupy.
|
||||
|
||||
These tests verify that API responses maintain consistent structure and format
|
||||
by comparing against stored snapshots. This catches unexpected changes to
|
||||
response schemas.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import pytest
|
||||
from types import SimpleNamespace
|
||||
from syrupy import SnapshotAssertion
|
||||
|
||||
from py.routes.handlers.misc_handlers import (
|
||||
ModelLibraryHandler,
|
||||
NodeRegistry,
|
||||
NodeRegistryHandler,
|
||||
ServiceRegistryAdapter,
|
||||
SettingsHandler,
|
||||
)
|
||||
from py.utils.utils import calculate_recipe_fingerprint, sanitize_folder_name
|
||||
|
||||
|
||||
class FakeRequest:
|
||||
"""Fake HTTP request for testing."""
|
||||
|
||||
def __init__(self, *, json_data=None, query=None):
|
||||
self._json_data = json_data or {}
|
||||
self.query = query or {}
|
||||
|
||||
async def json(self):
|
||||
return self._json_data
|
||||
|
||||
|
||||
class DummySettings:
|
||||
"""Dummy settings service for testing."""
|
||||
|
||||
def __init__(self, data=None):
|
||||
self.data = data or {}
|
||||
|
||||
def get(self, key, default=None):
|
||||
return self.data.get(key, default)
|
||||
|
||||
def set(self, key, value):
|
||||
self.data[key] = value
|
||||
|
||||
|
||||
async def noop_async(*_args, **_kwargs):
|
||||
"""No-op async function."""
|
||||
return None
|
||||
|
||||
|
||||
class FakePromptServer:
|
||||
"""Fake prompt server for testing."""
|
||||
|
||||
sent = []
|
||||
|
||||
class Instance:
|
||||
def send_sync(self, event, payload):
|
||||
FakePromptServer.sent.append((event, payload))
|
||||
|
||||
instance = Instance()
|
||||
|
||||
|
||||
class TestSettingsHandlerSnapshots:
|
||||
"""Snapshot tests for SettingsHandler responses."""
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_settings_response_format(self, snapshot: SnapshotAssertion):
|
||||
"""Verify get_settings response format matches snapshot."""
|
||||
settings_service = DummySettings({
|
||||
"civitai_api_key": "test-key",
|
||||
"language": "en",
|
||||
"theme": "dark"
|
||||
})
|
||||
handler = SettingsHandler(
|
||||
settings_service=settings_service,
|
||||
metadata_provider_updater=noop_async,
|
||||
downloader_factory=lambda: None,
|
||||
)
|
||||
|
||||
response = await handler.get_settings(FakeRequest())
|
||||
payload = json.loads(response.text)
|
||||
|
||||
assert payload == snapshot
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_update_settings_success_response(self, snapshot: SnapshotAssertion):
|
||||
"""Verify successful update_settings response format."""
|
||||
settings_service = DummySettings()
|
||||
handler = SettingsHandler(
|
||||
settings_service=settings_service,
|
||||
metadata_provider_updater=noop_async,
|
||||
downloader_factory=lambda: None,
|
||||
)
|
||||
|
||||
request = FakeRequest(json_data={"language": "zh"})
|
||||
response = await handler.update_settings(request)
|
||||
payload = json.loads(response.text)
|
||||
|
||||
assert payload == snapshot
|
||||
|
||||
|
||||
class TestNodeRegistryHandlerSnapshots:
|
||||
"""Snapshot tests for NodeRegistryHandler responses."""
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_register_nodes_success_response(self, snapshot: SnapshotAssertion):
|
||||
"""Verify successful register_nodes response format."""
|
||||
node_registry = NodeRegistry()
|
||||
handler = NodeRegistryHandler(
|
||||
node_registry=node_registry,
|
||||
prompt_server=FakePromptServer,
|
||||
standalone_mode=False,
|
||||
)
|
||||
|
||||
request = FakeRequest(
|
||||
json_data={
|
||||
"nodes": [
|
||||
{
|
||||
"node_id": 1,
|
||||
"graph_id": "root",
|
||||
"type": "Lora Loader (LoraManager)",
|
||||
"title": "Test Loader",
|
||||
}
|
||||
]
|
||||
}
|
||||
)
|
||||
|
||||
response = await handler.register_nodes(request)
|
||||
payload = json.loads(response.text)
|
||||
|
||||
assert payload == snapshot
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_register_nodes_error_response(self, snapshot: SnapshotAssertion):
|
||||
"""Verify error register_nodes response format."""
|
||||
node_registry = NodeRegistry()
|
||||
handler = NodeRegistryHandler(
|
||||
node_registry=node_registry,
|
||||
prompt_server=FakePromptServer,
|
||||
standalone_mode=False,
|
||||
)
|
||||
|
||||
request = FakeRequest(json_data={"nodes": []})
|
||||
response = await handler.register_nodes(request)
|
||||
payload = json.loads(response.text)
|
||||
|
||||
assert payload == snapshot
|
||||
|
||||
|
||||
class TestUtilityFunctionSnapshots:
|
||||
"""Snapshot tests for utility function outputs."""
|
||||
|
||||
def test_sanitize_folder_name_various_inputs(self, snapshot: SnapshotAssertion):
|
||||
"""Verify sanitize_folder_name produces expected outputs."""
|
||||
test_inputs = [
|
||||
"normal_folder",
|
||||
"folder with spaces",
|
||||
"folder/with/slashes",
|
||||
'folder\\with\\backslashes',
|
||||
'folder<with>brackets',
|
||||
'folder"with"quotes',
|
||||
'folder|with|pipes',
|
||||
'folder?with?questions',
|
||||
'folder*with*asterisks',
|
||||
'',
|
||||
' spaces ',
|
||||
'folder.with.dots',
|
||||
'___underscores___',
|
||||
]
|
||||
|
||||
results = {input_name: sanitize_folder_name(input_name) for input_name in test_inputs}
|
||||
assert results == snapshot
|
||||
|
||||
def test_calculate_recipe_fingerprint_various_inputs(self, snapshot: SnapshotAssertion):
|
||||
"""Verify calculate_recipe_fingerprint produces expected outputs."""
|
||||
test_cases = [
|
||||
[],
|
||||
[{"hash": "abc123", "strength": 1.0}],
|
||||
[
|
||||
{"hash": "abc123", "strength": 1.0},
|
||||
{"hash": "def456", "strength": 0.75},
|
||||
],
|
||||
[
|
||||
{"hash": "DEF456", "strength": 1.0},
|
||||
{"hash": "ABC123", "strength": 0.5},
|
||||
],
|
||||
[{"hash": "abc123", "weight": 0.8}],
|
||||
[{"modelVersionId": 12345, "strength": 1.0}],
|
||||
[{"hash": "abc123", "exclude": True, "strength": 1.0}],
|
||||
[{"hash": "", "strength": 1.0}],
|
||||
[{"strength": 1.0}],
|
||||
]
|
||||
|
||||
results = [calculate_recipe_fingerprint(loras) for loras in test_cases]
|
||||
assert results == snapshot
|
||||
|
||||
|
||||
class TestModelLibraryHandlerSnapshots:
|
||||
"""Snapshot tests for ModelLibraryHandler responses."""
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_check_model_exists_empty_response(self, snapshot: SnapshotAssertion):
|
||||
"""Verify check_model_exists with no versions response format."""
|
||||
|
||||
class EmptyVersionScanner:
|
||||
async def check_model_version_exists(self, _version_id):
|
||||
return False
|
||||
|
||||
async def get_model_versions_by_id(self, _model_id):
|
||||
return []
|
||||
|
||||
async def scanner_factory():
|
||||
return EmptyVersionScanner()
|
||||
|
||||
handler = ModelLibraryHandler(
|
||||
ServiceRegistryAdapter(
|
||||
get_lora_scanner=scanner_factory,
|
||||
get_checkpoint_scanner=scanner_factory,
|
||||
get_embedding_scanner=scanner_factory,
|
||||
),
|
||||
metadata_provider_factory=lambda: None,
|
||||
)
|
||||
|
||||
response = await handler.check_model_exists(FakeRequest(query={"modelId": "1"}))
|
||||
payload = json.loads(response.text)
|
||||
|
||||
assert payload == snapshot
|
||||
Reference in New Issue
Block a user