diff --git a/core/services/scanner_service.py b/core/services/scanner_service.py index 5faad4a..2270208 100644 --- a/core/services/scanner_service.py +++ b/core/services/scanner_service.py @@ -7,16 +7,16 @@ class ScannerService: """Coordinate local model listing and path-template discovery.""" def __init__(self, context: RouteContext): - self.asyncio = context.get("asyncio") - self.get_model_files = context.get("get_model_files") - self.infer_download_path_templates = context.get( + self.asyncio = context.require("asyncio") + self.get_model_files = context.require("get_model_files") + self.infer_download_path_templates = context.require( "infer_download_path_templates" ) - self.invalidate_local_hash_match_cache = context.get( + self.invalidate_local_hash_match_cache = context.require( "invalidate_local_hash_match_cache" ) - self.to_bool = context.get("to_bool") - self.web = context.get("web") + self.to_bool = context.require("to_bool") + self.web = context.require("web") async def get_models(self, request): """Return the locally available models, optionally forcing a rescan.""" diff --git a/tests/test_directory_routes.py b/tests/test_directory_routes.py index 66962b2..9836c54 100644 --- a/tests/test_directory_routes.py +++ b/tests/test_directory_routes.py @@ -11,6 +11,7 @@ from aiohttp import web from core.routes.context import RouteContext from core.routes.directories import register_directory_routes +from core.type_utils import to_bool class _Routes: @@ -58,6 +59,7 @@ def _build_routes(**overrides): ), "get_model_files": MagicMock(return_value=[]), "infer_download_path_templates": MagicMock(return_value=[]), + "invalidate_local_hash_match_cache": MagicMock(), "is_civarchive_available": MagicMock(return_value=False), "is_lora_manager_archive_available": MagicMock(return_value=False), "json_api_endpoint": _json_api_endpoint, @@ -70,6 +72,7 @@ def _build_routes(**overrides): "split_path_segments": lambda value: [ part for part in str(value).replace("\\", "/").split("/") if part ], + "to_bool": to_bool, "web": web, } values.update(overrides) diff --git a/tests/test_route_context_contract.py b/tests/test_route_context_contract.py new file mode 100644 index 0000000..4e0249c --- /dev/null +++ b/tests/test_route_context_contract.py @@ -0,0 +1,19 @@ +import pytest + +from core.routes.context import RouteContext +from core.services.scanner_service import ScannerService + + +def test_route_context_remains_immutable_after_construction(): + values = {"dependency": "original"} + context = RouteContext(values) + values["dependency"] = "changed" + + assert context.get("dependency") == "original" + with pytest.raises(KeyError, match="Missing route dependency: absent"): + context.require("absent") + + +def test_scanner_service_requires_mandatory_route_dependencies(): + with pytest.raises(KeyError, match="Missing route dependency: asyncio"): + ScannerService(RouteContext({})) diff --git a/tests/test_scanner_routes.py b/tests/test_scanner_routes.py index da7136e..fe3afa0 100644 --- a/tests/test_scanner_routes.py +++ b/tests/test_scanner_routes.py @@ -46,6 +46,7 @@ def _build_metadata_routes(): "get_model_files": MagicMock( return_value=[{"filename": "model.safetensors"}] ), + "infer_download_path_templates": MagicMock(return_value=[]), "get_progress_response": MagicMock(), "invalidate_local_hash_match_cache": MagicMock(), "json_api_endpoint": _json_api_endpoint, @@ -86,6 +87,7 @@ def _build_directory_routes(): "infer_download_path_templates": MagicMock( return_value=[{"category": "checkpoints"}] ), + "invalidate_local_hash_match_cache": MagicMock(), "is_civarchive_available": MagicMock(return_value=False), "is_lora_manager_archive_available": MagicMock(return_value=False), "json_api_endpoint": _json_api_endpoint, @@ -98,6 +100,7 @@ def _build_directory_routes(): "routes": routes, "self": SimpleNamespace(logger=MagicMock()), "split_path_segments": MagicMock(return_value=[]), + "to_bool": to_bool, "web": web, } register_directory_routes(RouteContext(values))