import logging from typing import Any, Dict, Iterable, List import pytest from py import config as config_module from py.services import settings_manager as settings_manager_module def _setup_config_environment(monkeypatch: pytest.MonkeyPatch, tmp_path) -> Dict[str, List[str]]: loras_dir = tmp_path / "loras" checkpoints_dir = tmp_path / "checkpoints" embeddings_dir = tmp_path / "embeddings" for directory in (loras_dir, checkpoints_dir, embeddings_dir): directory.mkdir() folder_paths: Dict[str, List[str]] = { "loras": [str(loras_dir)], "checkpoints": [str(checkpoints_dir)], "unet": [], "embeddings": [str(embeddings_dir)], } def fake_get_folder_paths(kind: str) -> Iterable[str]: return folder_paths.get(kind, []) monkeypatch.setattr(config_module.folder_paths, "get_folder_paths", fake_get_folder_paths) monkeypatch.setattr(config_module, "standalone_mode", False) monkeypatch.setattr( config_module, "ensure_settings_file", lambda logger=None: str(tmp_path / "settings.json"), ) return folder_paths def test_save_paths_renames_default_library(monkeypatch: pytest.MonkeyPatch, tmp_path): folder_paths = _setup_config_environment(monkeypatch, tmp_path) class FakeSettingsService: def __init__(self, default_paths: Dict[str, List[str]]): self._default_paths = default_paths self.rename_calls = [] self.delete_calls = [] self.upsert_calls = [] self._renamed = False self.active_library = "default" def get_libraries(self): if self._renamed: return {"comfyui": {}} return { "default": { "folder_paths": {key: list(value) for key, value in self._default_paths.items()}, "default_lora_root": "", "default_checkpoint_root": "", "default_embedding_root": "", } } def rename_library(self, old_name: str, new_name: str): self.rename_calls.append((old_name, new_name)) self._renamed = True if self.active_library == old_name: self.active_library = new_name def get_active_library_name(self): return self.active_library def delete_library(self, name: str): # pragma: no cover - defensive guard self.delete_calls.append(name) raise AssertionError("delete_library should not be invoked in this scenario") def upsert_library(self, name: str, **payload): self.upsert_calls.append((name, payload)) fake_settings = FakeSettingsService(folder_paths) monkeypatch.setattr(settings_manager_module, "settings", fake_settings) config_instance = config_module.Config() assert isinstance(config_instance, config_module.Config) assert fake_settings.rename_calls == [("default", "comfyui")] assert len(fake_settings.upsert_calls) == 1 name, payload = fake_settings.upsert_calls[0] assert name == "comfyui" # The Config class normalizes paths to use forward slashes for cross-platform compatibility # Convert expected paths to the same format for comparison expected_folder_paths = { key: [path.replace("\\", "/") for path in paths] for key, paths in folder_paths.items() } assert payload["folder_paths"] == expected_folder_paths assert payload["default_lora_root"] == folder_paths["loras"][0].replace("\\", "/") assert payload["default_checkpoint_root"] == folder_paths["checkpoints"][0].replace("\\", "/") assert payload["default_embedding_root"] == folder_paths["embeddings"][0].replace("\\", "/") assert payload["metadata"] == {"display_name": "ComfyUI", "source": "comfyui"} assert payload["activate"] is True def test_save_paths_logs_warning_when_upsert_fails( monkeypatch: pytest.MonkeyPatch, tmp_path, caplog ): folder_paths = _setup_config_environment(monkeypatch, tmp_path) class RaisingSettingsService: def __init__(self): self.upsert_attempts = [] self.active_library = "comfyui" def get_libraries(self): return { "comfyui": { "folder_paths": {key: list(value) for key, value in folder_paths.items()}, "default_lora_root": "existing", } } def rename_library(self, *_): raise AssertionError("rename_library should not be invoked") def get_active_library_name(self): return self.active_library def upsert_library(self, name: str, **payload): self.upsert_attempts.append((name, payload)) raise RuntimeError("boom") fake_settings = RaisingSettingsService() monkeypatch.setattr(settings_manager_module, "settings", fake_settings) with caplog.at_level(logging.WARNING, logger=config_module.logger.name): config_instance = config_module.Config() assert isinstance(config_instance, config_module.Config) assert fake_settings.upsert_attempts and fake_settings.upsert_attempts[0][0] == "comfyui" assert "Failed to save folder paths: boom" in caplog.text def test_save_paths_repairs_empty_default_roots(monkeypatch: pytest.MonkeyPatch, tmp_path): folder_paths = _setup_config_environment(monkeypatch, tmp_path) class FakeSettingsService: active_library = "comfyui" name: str = "" payload: Dict[str, Any] = {} def get_libraries(self): return { "comfyui": { "folder_paths": {key: list(value) for key, value in folder_paths.items()}, "default_lora_root": "", "default_checkpoint_root": "", "default_embedding_root": "", } } def rename_library(self, *_): raise AssertionError("rename_library should not be invoked") def get_active_library_name(self): return self.active_library def upsert_library(self, name: str, **payload): self.name = name self.payload = payload fake_settings = FakeSettingsService() monkeypatch.setattr(settings_manager_module, "settings", fake_settings) config_module.Config() assert fake_settings.name == "comfyui" assert fake_settings.payload["default_lora_root"] == folder_paths["loras"][0].replace("\\", "/") assert fake_settings.payload["default_checkpoint_root"] == folder_paths["checkpoints"][0].replace("\\", "/") assert fake_settings.payload["default_embedding_root"] == folder_paths["embeddings"][0].replace("\\", "/") def test_save_paths_repairs_stale_default_roots(monkeypatch: pytest.MonkeyPatch, tmp_path): folder_paths = _setup_config_environment(monkeypatch, tmp_path) class FakeSettingsService: active_library = "comfyui" name: str = "" payload: Dict[str, Any] = {} def get_libraries(self): return { "comfyui": { "folder_paths": {key: list(value) for key, value in folder_paths.items()}, "default_lora_root": "/stale/loras", "default_checkpoint_root": "/stale/checkpoints", "default_embedding_root": "/stale/embeddings", } } def rename_library(self, *_): raise AssertionError("rename_library should not be invoked") def get_active_library_name(self): return self.active_library def upsert_library(self, name: str, **payload): self.name = name self.payload = payload fake_settings = FakeSettingsService() monkeypatch.setattr(settings_manager_module, "settings", fake_settings) config_module.Config() assert fake_settings.name == "comfyui" assert fake_settings.payload["default_lora_root"] == folder_paths["loras"][0].replace("\\", "/") assert fake_settings.payload["default_checkpoint_root"] == folder_paths["checkpoints"][0].replace("\\", "/") assert fake_settings.payload["default_embedding_root"] == folder_paths["embeddings"][0].replace("\\", "/") def test_save_paths_keeps_valid_default_roots(monkeypatch: pytest.MonkeyPatch, tmp_path): folder_paths = _setup_config_environment(monkeypatch, tmp_path) class FakeSettingsService: active_library = "comfyui" name: str = "" payload: Dict[str, Any] = {} def get_libraries(self): return { "comfyui": { "folder_paths": {key: list(value) for key, value in folder_paths.items()}, "default_lora_root": folder_paths["loras"][0], "default_checkpoint_root": folder_paths["checkpoints"][0], "default_embedding_root": folder_paths["embeddings"][0], } } def rename_library(self, *_): raise AssertionError("rename_library should not be invoked") def get_active_library_name(self): return self.active_library def upsert_library(self, name: str, **payload): self.name = name self.payload = payload fake_settings = FakeSettingsService() monkeypatch.setattr(settings_manager_module, "settings", fake_settings) config_module.Config() assert fake_settings.name == "comfyui" assert fake_settings.payload["default_lora_root"] == folder_paths["loras"][0].replace("\\", "/") assert fake_settings.payload["default_checkpoint_root"] == folder_paths["checkpoints"][0].replace("\\", "/") assert fake_settings.payload["default_embedding_root"] == folder_paths["embeddings"][0].replace("\\", "/") def test_save_paths_removes_template_default_library(monkeypatch, tmp_path): folder_paths = _setup_config_environment(monkeypatch, tmp_path) placeholder_paths = { "loras": [ "C:/path/to/your/loras_folder", "C:/path/to/another/loras_folder", ], "checkpoints": [ "C:/path/to/your/checkpoints_folder", "C:/path/to/another/checkpoints_folder", ], "embeddings": [ "C:/path/to/your/embeddings_folder", "C:/path/to/another/embeddings_folder", ], } class FakeSettingsService: def __init__(self): self.libraries = { "default": { "folder_paths": placeholder_paths, "default_lora_root": "", "default_checkpoint_root": "", "default_embedding_root": "", } } self.rename_calls = [] self.delete_calls = [] self.upsert_calls = [] self.active_library = "default" def get_libraries(self): return self.libraries def rename_library(self, old_name: str, new_name: str): self.rename_calls.append((old_name, new_name)) self.libraries[new_name] = self.libraries.pop(old_name) if self.active_library == old_name: self.active_library = new_name def delete_library(self, name: str): self.delete_calls.append(name) self.libraries.pop(name, None) def upsert_library(self, name: str, **payload): self.upsert_calls.append((name, payload)) self.libraries[name] = {**payload} if payload.get("activate"): self.active_library = name def get_active_library_name(self): return self.active_library fake_settings = FakeSettingsService() monkeypatch.setattr(settings_manager_module, "settings", fake_settings) monkeypatch.setattr( config_module, "load_settings_template", lambda: {"folder_paths": placeholder_paths}, ) config_instance = config_module.Config() assert isinstance(config_instance, config_module.Config) assert fake_settings.rename_calls == [("default", "comfyui")] assert not fake_settings.delete_calls assert len(fake_settings.upsert_calls) == 1 assert "default" not in fake_settings.libraries assert set(fake_settings.libraries.keys()) == {"comfyui"} name, payload = fake_settings.upsert_calls[0] assert name == "comfyui" expected_folder_paths = { key: [path.replace("\\", "/") for path in paths] for key, paths in folder_paths.items() } assert payload["folder_paths"] == expected_folder_paths assert payload["default_lora_root"] == folder_paths["loras"][0].replace("\\", "/") assert ( payload["default_checkpoint_root"] == folder_paths["checkpoints"][0].replace("\\", "/") ) assert ( payload["default_embedding_root"] == folder_paths["embeddings"][0].replace("\\", "/") ) assert payload["metadata"] == {"display_name": "ComfyUI", "source": "comfyui"} assert payload["activate"] is True def test_save_paths_keeps_default_roots_in_extra_paths(monkeypatch: pytest.MonkeyPatch, tmp_path): folder_paths = _setup_config_environment(monkeypatch, tmp_path) extra_lora_dir = tmp_path / "extra_loras" extra_checkpoint_dir = tmp_path / "extra_checkpoints" extra_embedding_dir = tmp_path / "extra_embeddings" for directory in (extra_lora_dir, extra_checkpoint_dir, extra_embedding_dir): directory.mkdir() class FakeSettingsService: active_library = "comfyui" name: str = "" payload: Dict[str, Any] = {} def get_libraries(self): return { "comfyui": { "folder_paths": {key: list(value) for key, value in folder_paths.items()}, "extra_folder_paths": { "loras": [str(extra_lora_dir)], "checkpoints": [str(extra_checkpoint_dir)], "embeddings": [str(extra_embedding_dir)], }, "default_lora_root": str(extra_lora_dir), "default_checkpoint_root": str(extra_checkpoint_dir), "default_embedding_root": str(extra_embedding_dir), } } def rename_library(self, *_): raise AssertionError("rename_library should not be invoked") def get_active_library_name(self): return self.active_library def upsert_library(self, name: str, **payload): self.name = name self.payload = payload fake_settings = FakeSettingsService() monkeypatch.setattr(settings_manager_module, "settings", fake_settings) config_module.Config() assert fake_settings.name == "comfyui" assert fake_settings.payload["extra_folder_paths"]["loras"] == [str(extra_lora_dir).replace("\\", "/")] assert fake_settings.payload["extra_folder_paths"]["checkpoints"] == [ str(extra_checkpoint_dir).replace("\\", "/") ] assert fake_settings.payload["extra_folder_paths"]["embeddings"] == [ str(extra_embedding_dir).replace("\\", "/") ] assert fake_settings.payload["default_lora_root"] == str(extra_lora_dir).replace("\\", "/") assert fake_settings.payload["default_checkpoint_root"] == str(extra_checkpoint_dir).replace("\\", "/") assert fake_settings.payload["default_embedding_root"] == str(extra_embedding_dir).replace("\\", "/") assert fake_settings.payload["activate"] is True def test_save_paths_keeps_default_roots_in_extra_paths_with_windows_slash_mismatch( monkeypatch: pytest.MonkeyPatch, tmp_path ): folder_paths = _setup_config_environment(monkeypatch, tmp_path) class FakeSettingsService: active_library = "comfyui" name: str = "" payload: Dict[str, Any] = {} def get_libraries(self): return { "comfyui": { "folder_paths": {key: list(value) for key, value in folder_paths.items()}, "extra_folder_paths": { "loras": ["U:\\Lora7\\Loras"], "checkpoints": ["U:\\Lora7\\Models"], "embeddings": [], }, "default_lora_root": "U:/Lora7/Loras", "default_checkpoint_root": "U:/Lora7/Models", "default_embedding_root": folder_paths["embeddings"][0], } } def rename_library(self, *_): raise AssertionError("rename_library should not be invoked") def get_active_library_name(self): return self.active_library def upsert_library(self, name: str, **payload): self.name = name self.payload = payload fake_settings = FakeSettingsService() monkeypatch.setattr(settings_manager_module, "settings", fake_settings) config_module.Config() assert fake_settings.name == "comfyui" assert fake_settings.payload["default_lora_root"] == "U:/Lora7/Loras" assert fake_settings.payload["default_checkpoint_root"] == "U:/Lora7/Models" def test_save_paths_repairs_empty_default_roots_to_extra_paths_when_primary_missing( monkeypatch: pytest.MonkeyPatch, tmp_path ): _setup_config_environment(monkeypatch, tmp_path) extra_lora_dir = tmp_path / "extra_loras" extra_lora_dir.mkdir() monkeypatch.setattr( config_module.folder_paths, "get_folder_paths", lambda kind: [] if kind == "loras" else [], ) class FakeSettingsService: active_library = "comfyui" name: str = "" payload: Dict[str, Any] = {} def get_libraries(self): return { "comfyui": { "folder_paths": { "loras": [], "checkpoints": [], "unet": [], "embeddings": [], }, "extra_folder_paths": { "loras": [str(extra_lora_dir)], }, "default_lora_root": "", } } def rename_library(self, *_): raise AssertionError("rename_library should not be invoked") def get_active_library_name(self): return self.active_library def upsert_library(self, name: str, **payload): self.name = name self.payload = payload fake_settings = FakeSettingsService() monkeypatch.setattr(settings_manager_module, "settings", fake_settings) config_module.Config() assert fake_settings.name == "comfyui" assert fake_settings.payload["default_lora_root"] == str(extra_lora_dir).replace("\\", "/") def test_save_paths_does_not_activate_comfyui_library_when_another_library_is_active( monkeypatch: pytest.MonkeyPatch, tmp_path ): folder_paths = _setup_config_environment(monkeypatch, tmp_path) class FakeSettingsService: def __init__(self): self.active_library = "studio" self.upsert_calls = [] def get_libraries(self): return { "studio": { "folder_paths": {"loras": ["/studio/loras"]}, }, "comfyui": { "folder_paths": {key: list(value) for key, value in folder_paths.items()}, "default_lora_root": folder_paths["loras"][0], "default_checkpoint_root": folder_paths["checkpoints"][0], "default_embedding_root": folder_paths["embeddings"][0], }, } def rename_library(self, *_): raise AssertionError("rename_library should not be invoked") def get_active_library_name(self): return self.active_library def upsert_library(self, name: str, **payload): self.upsert_calls.append((name, payload)) fake_settings = FakeSettingsService() monkeypatch.setattr(settings_manager_module, "settings", fake_settings) config_module.Config() assert len(fake_settings.upsert_calls) == 1 name, payload = fake_settings.upsert_calls[0] assert name == "comfyui" assert payload["activate"] is False def test_apply_library_settings_merges_extra_paths(monkeypatch, tmp_path): """Test that apply_library_settings correctly merges folder_paths with extra_folder_paths.""" loras_dir = tmp_path / "loras" extra_loras_dir = tmp_path / "extra_loras" checkpoints_dir = tmp_path / "checkpoints" extra_checkpoints_dir = tmp_path / "extra_checkpoints" embeddings_dir = tmp_path / "embeddings" extra_embeddings_dir = tmp_path / "extra_embeddings" for directory in (loras_dir, extra_loras_dir, checkpoints_dir, extra_checkpoints_dir, embeddings_dir, extra_embeddings_dir): directory.mkdir() config_instance = config_module.Config() folder_paths = { "loras": [str(loras_dir)], "checkpoints": [str(checkpoints_dir)], "unet": [], "embeddings": [str(embeddings_dir)], } extra_folder_paths = { "loras": [str(extra_loras_dir)], "checkpoints": [str(extra_checkpoints_dir)], "unet": [], "embeddings": [str(extra_embeddings_dir)], } library_config = { "folder_paths": folder_paths, "extra_folder_paths": extra_folder_paths, } config_instance.apply_library_settings(library_config) assert str(loras_dir) in config_instance.loras_roots assert str(extra_loras_dir) in config_instance.extra_loras_roots assert str(checkpoints_dir) in config_instance.base_models_roots assert str(extra_checkpoints_dir) in config_instance.extra_checkpoints_roots assert str(embeddings_dir) in (config_instance.embeddings_roots or []) assert str(extra_embeddings_dir) in config_instance.extra_embeddings_roots def test_apply_library_settings_without_extra_paths(monkeypatch, tmp_path): """Test that apply_library_settings works when extra_folder_paths is not provided.""" loras_dir = tmp_path / "loras" checkpoints_dir = tmp_path / "checkpoints" embeddings_dir = tmp_path / "embeddings" for directory in (loras_dir, checkpoints_dir, embeddings_dir): directory.mkdir() config_instance = config_module.Config() folder_paths = { "loras": [str(loras_dir)], "checkpoints": [str(checkpoints_dir)], "unet": [], "embeddings": [str(embeddings_dir)], } library_config = { "folder_paths": folder_paths, } config_instance.apply_library_settings(library_config) assert str(loras_dir) in config_instance.loras_roots assert config_instance.extra_loras_roots == [] assert str(checkpoints_dir) in config_instance.base_models_roots assert config_instance.extra_checkpoints_roots == [] assert str(embeddings_dir) in (config_instance.embeddings_roots or []) assert config_instance.extra_embeddings_roots == [] def test_extra_paths_deduplication(monkeypatch, tmp_path): """Test that extra paths are stored separately from main paths in Config.""" loras_dir = tmp_path / "loras" extra_loras_dir = tmp_path / "extra_loras" loras_dir.mkdir() extra_loras_dir.mkdir() config_instance = config_module.Config() folder_paths = { "loras": [str(loras_dir)], "checkpoints": [], "unet": [], "embeddings": [], } extra_folder_paths = { "loras": [str(extra_loras_dir)], "checkpoints": [], "unet": [], "embeddings": [], } library_config = { "folder_paths": folder_paths, "extra_folder_paths": extra_folder_paths, } config_instance.apply_library_settings(library_config) assert config_instance.loras_roots == [str(loras_dir)] assert config_instance.extra_loras_roots == [str(extra_loras_dir)] def test_apply_library_settings_ignores_extra_lora_path_overlapping_primary_symlink( monkeypatch, tmp_path, caplog ): """Extra LoRA paths should be ignored when they resolve to the same target as a primary root.""" real_loras_dir = tmp_path / "loras_real" real_loras_dir.mkdir() loras_link = tmp_path / "loras_link" loras_link.symlink_to(real_loras_dir, target_is_directory=True) config_instance = config_module.Config() library_config = { "folder_paths": { "loras": [str(loras_link)], "checkpoints": [], "unet": [], "embeddings": [], }, "extra_folder_paths": { "loras": [str(real_loras_dir)], "checkpoints": [], "unet": [], "embeddings": [], }, } with caplog.at_level("WARNING", logger=config_module.logger.name): config_instance.apply_library_settings(library_config) assert config_instance.loras_roots == [str(loras_link)] assert config_instance.extra_loras_roots == [] warning_messages = [ record.message for record in caplog.records if record.levelname == "WARNING" and "same lora folder" in record.message.lower() ] assert len(warning_messages) == 1 assert "comfyui model paths" in warning_messages[0].lower() assert "extra folder paths" in warning_messages[0].lower() assert "duplicate items" in warning_messages[0].lower() def test_apply_library_settings_detects_overlap_case_insensitively( monkeypatch, tmp_path, caplog ): """Overlap detection should use case-insensitive comparison on Windows-like paths.""" real_loras_dir = tmp_path / "loras_real" real_loras_dir.mkdir() loras_link = tmp_path / "loras_link" loras_link.symlink_to(real_loras_dir, target_is_directory=True) original_exists = config_module.os.path.exists original_realpath = config_module.os.path.realpath original_normcase = config_module.os.path.normcase def fake_exists(path): if isinstance(path, str) and path.lower() in { str(loras_link).lower(), str(real_loras_dir).lower(), str(loras_link).upper().lower(), str(real_loras_dir).upper().lower(), }: return True return original_exists(path) def fake_realpath(path, *args, **kwargs): if isinstance(path, str): lowered = path.lower() if lowered == str(loras_link).lower(): return str(real_loras_dir) if lowered == str(real_loras_dir).lower(): return str(real_loras_dir) return original_realpath(path, *args, **kwargs) monkeypatch.setattr(config_module.os.path, "exists", fake_exists) monkeypatch.setattr(config_module.os.path, "realpath", fake_realpath) monkeypatch.setattr( config_module.os.path, "normcase", lambda value: original_normcase(value).lower(), ) config_instance = config_module.Config() primary_path = str(loras_link).replace("loras_link", "LORAS_LINK") extra_path = str(real_loras_dir).replace("loras_real", "loras_real") library_config = { "folder_paths": { "loras": [primary_path], "checkpoints": [], "unet": [], "embeddings": [], }, "extra_folder_paths": { "loras": [extra_path.upper()], "checkpoints": [], "unet": [], "embeddings": [], }, } with caplog.at_level("WARNING", logger=config_module.logger.name): config_instance.apply_library_settings(library_config) assert config_instance.loras_roots == [primary_path] assert config_instance.extra_loras_roots == [] assert any("same lora folder" in record.message.lower() for record in caplog.records) def test_apply_library_settings_ignores_missing_extra_lora_paths(monkeypatch, tmp_path, caplog): """Missing extra paths should be ignored without overlap warnings.""" loras_dir = tmp_path / "loras" loras_dir.mkdir() missing_extra = tmp_path / "missing_loras" config_instance = config_module.Config() library_config = { "folder_paths": { "loras": [str(loras_dir)], "checkpoints": [], "unet": [], "embeddings": [], }, "extra_folder_paths": { "loras": [str(missing_extra)], "checkpoints": [], "unet": [], "embeddings": [], }, } with caplog.at_level("WARNING", logger=config_module.logger.name): config_instance.apply_library_settings(library_config) assert config_instance.loras_roots == [str(loras_dir)] assert config_instance.extra_loras_roots == [] assert not any("same lora folder" in record.message.lower() for record in caplog.records) def test_apply_library_settings_ignores_extra_lora_path_overlapping_primary_root_symlink( tmp_path, caplog ): """Extra LoRA paths should be ignored when already reachable via a first-level symlink under the primary root.""" loras_dir = tmp_path / "loras" loras_dir.mkdir() external_dir = tmp_path / "external_loras" external_dir.mkdir() link_dir = loras_dir / "link" link_dir.symlink_to(external_dir, target_is_directory=True) config_instance = config_module.Config() library_config = { "folder_paths": { "loras": [str(loras_dir)], "checkpoints": [], "unet": [], "embeddings": [], }, "extra_folder_paths": { "loras": [str(external_dir)], "checkpoints": [], "unet": [], "embeddings": [], }, } with caplog.at_level("WARNING", logger=config_module.logger.name): config_instance.apply_library_settings(library_config) assert config_instance.loras_roots == [str(loras_dir)] assert config_instance.extra_loras_roots == [] assert any( "same lora folder" in record.message.lower() for record in caplog.records ) def test_save_paths_removes_stale_empty_default_when_comfyui_exists( monkeypatch: pytest.MonkeyPatch, tmp_path, ): """When an empty-shell 'default' library coexists with 'comfyui', the stale 'default' entry should be removed and 'comfyui' activated.""" folder_paths = _setup_config_environment(monkeypatch, tmp_path) class FakeSettingsService: def __init__(self): # Replicate the user's settings.json: empty default + populated comfyui self.libraries = { "default": { "folder_paths": {}, "extra_folder_paths": {}, "default_lora_root": "", "default_checkpoint_root": "", "default_unet_root": "", "default_embedding_root": "", "recipes_path": "", }, "comfyui": { "folder_paths": { key: list(value) for key, value in folder_paths.items() }, "default_lora_root": folder_paths["loras"][0], "default_checkpoint_root": folder_paths["checkpoints"][0], "default_embedding_root": folder_paths["embeddings"][0], }, } # No active_library key — get_active_library_name() falls back to # dict order, returning "default". self.active_library = "default" self.delete_calls: list[str] = [] self.upsert_calls: list[tuple[str, dict[str, Any]]] = [] def get_libraries(self): return dict(self.libraries) def delete_library(self, name: str): self.delete_calls.append(name) self.libraries.pop(name, None) def rename_library(self, *_): raise AssertionError("rename_library should not be invoked") def get_active_library_name(self): return self.active_library def upsert_library(self, name: str, **payload): self.upsert_calls.append((name, payload)) self.libraries[name] = {**payload} if payload.get("activate"): self.active_library = name fake_settings = FakeSettingsService() monkeypatch.setattr(settings_manager_module, "settings", fake_settings) config_module.Config() assert fake_settings.delete_calls == ["default"] assert "default" not in fake_settings.libraries assert set(fake_settings.libraries.keys()) == {"comfyui"} assert len(fake_settings.upsert_calls) == 1 name, payload = fake_settings.upsert_calls[0] assert name == "comfyui" assert payload["activate"] is True assert fake_settings.active_library == "comfyui" def test_save_paths_keeps_configured_root_of_a_switched_off_drive( monkeypatch: pytest.MonkeyPatch, tmp_path ): """A root whose directory is missing right now must stay in the library. `upsert_library(folder_paths=...)` replaces the stored paths, so persisting the existence-filtered live list would erase a drive that happened to be switched off when ComfyUI started. A path the host no longer configures must still be dropped. """ folder_paths = _setup_config_environment(monkeypatch, tmp_path) readable = folder_paths["loras"][0] switched_off = str(tmp_path / "drive-Z" / "loras") removed_by_user = str(tmp_path / "old-location" / "loras") # The host still configures the switched-off drive; it no longer configures # the old location (that one only survives in the stored library paths). folder_paths["loras"] = [switched_off, readable] class FakeSettingsService: active_library = "comfyui" name: str = "" payload: Dict[str, Any] = {} def get_libraries(self): return { "comfyui": { "folder_paths": { "loras": [removed_by_user, readable], "checkpoints": [], "unet": [], "embeddings": [], }, "default_lora_root": switched_off, } } def rename_library(self, *_): raise AssertionError("rename_library should not be invoked") def get_active_library_name(self): return self.active_library def upsert_library(self, name: str, **payload): self.name = name self.payload = payload fake_settings = FakeSettingsService() monkeypatch.setattr(settings_manager_module, "settings", fake_settings) config_instance = config_module.Config() persisted = fake_settings.payload["folder_paths"]["loras"] # The switched-off drive keeps its configured path... assert switched_off.replace("\\", "/") in persisted assert readable.replace("\\", "/") in persisted # ...the path the host really dropped is gone... assert removed_by_user.replace("\\", "/") not in persisted # ...and the live list still only holds what is readable right now. assert config_instance.loras_roots == [readable.replace("\\", "/")] # The user's default root survives because it is merely unavailable. assert fake_settings.payload["default_lora_root"] == switched_off.replace("\\", "/") # --- roots the host registered but never created (issue #1146) -------------- class _FakePersistentCache: """Stand-in for PersistentModelCache in the startup sync.""" def __init__(self, cached_roots=None, error: Exception | None = None): self._cached_roots = cached_roots or {} self._error = error self.queries: List[tuple] = [] def roots_with_cached_entries(self, model_type: str, roots): self.queries.append((model_type, list(roots))) if self._error is not None: raise self._error known = {path.replace("\\", "/") for path in self._cached_roots.get(model_type, [])} return { root for root in roots if root.replace("\\", "/") in known } def _stub_persistent_cache(monkeypatch: pytest.MonkeyPatch, cache) -> None: from py.services import persistent_model_cache as persistent_model_cache_module monkeypatch.setattr( persistent_model_cache_module, "get_persistent_cache", lambda: cache ) class _LibrarySettingsService: """Minimal settings service recording what the startup sync persists.""" active_library = "comfyui" def __init__(self, library: Dict[str, Any]): self._library = library self.payload: Dict[str, Any] = {} def get_libraries(self): return {"comfyui": self._library} def rename_library(self, *_): # pragma: no cover - defensive guard raise AssertionError("rename_library should not be invoked") def delete_library(self, *_): # pragma: no cover - defensive guard raise AssertionError("delete_library should not be invoked") def get_active_library_name(self): return self.active_library def upsert_library(self, name: str, **payload): self.payload = payload def test_save_paths_drops_registered_directories_that_never_existed( monkeypatch: pytest.MonkeyPatch, tmp_path ): """ComfyUI registers `/` for its save nodes unconditionally. Those directories are not library paths when they do not exist and hold no cached models: persisting them brings them back as "unavailable" roots on every boot. """ folder_paths = _setup_config_environment(monkeypatch, tmp_path) unet_dir = tmp_path / "unet" unet_dir.mkdir() folder_paths["unet"] = [str(unet_dir)] output_loras = str(tmp_path / "output" / "loras") output_checkpoints = str(tmp_path / "output" / "checkpoints") output_unet = str(tmp_path / "output" / "diffusion_models") output_embeddings = str(tmp_path / "output" / "embeddings") folder_paths["loras"].append(output_loras) folder_paths["checkpoints"].append(output_checkpoints) folder_paths["unet"].append(output_unet) folder_paths["embeddings"].append(output_embeddings) fake_settings = _LibrarySettingsService( { "folder_paths": {key: list(value) for key, value in folder_paths.items()}, "default_lora_root": folder_paths["loras"][0], "default_checkpoint_root": folder_paths["checkpoints"][0], "default_embedding_root": folder_paths["embeddings"][0], } ) monkeypatch.setattr(settings_manager_module, "settings", fake_settings) cache = _FakePersistentCache() _stub_persistent_cache(monkeypatch, cache) config_module.Config() persisted = fake_settings.payload["folder_paths"] for key, never_created in ( ("loras", output_loras), ("checkpoints", output_checkpoints), ("unet", output_unet), ("embeddings", output_embeddings), ): assert never_created.replace("\\", "/") not in persisted[key], key # The real root of that key is untouched. assert folder_paths[key][0].replace("\\", "/") in persisted[key], key # Lora and checkpoint roots are looked up in their own model type. assert [model_type for model_type, _roots in cache.queries] == [ "lora", "checkpoint", "checkpoint", "embedding", ] def test_save_paths_keeps_an_unavailable_root_that_has_cached_models( monkeypatch: pytest.MonkeyPatch, tmp_path ): """A switched-off drive keeps its path: its models are still cached.""" folder_paths = _setup_config_environment(monkeypatch, tmp_path) readable = folder_paths["loras"][0] switched_off = str(tmp_path / "drive-Z" / "loras") folder_paths["loras"] = [readable, switched_off] fake_settings = _LibrarySettingsService( { "folder_paths": {"loras": [readable]}, "default_lora_root": readable, } ) monkeypatch.setattr(settings_manager_module, "settings", fake_settings) _stub_persistent_cache( monkeypatch, _FakePersistentCache({"lora": [switched_off]}) ) config_module.Config() persisted = fake_settings.payload["folder_paths"]["loras"] assert switched_off.replace("\\", "/") in persisted assert readable.replace("\\", "/") in persisted def test_save_paths_keeps_every_root_when_the_cache_cannot_be_read( monkeypatch: pytest.MonkeyPatch, tmp_path ): """No answer from the cache means no root is dropped.""" folder_paths = _setup_config_environment(monkeypatch, tmp_path) readable = folder_paths["loras"][0] unknown = str(tmp_path / "drive-Z" / "loras") folder_paths["loras"] = [readable, unknown] fake_settings = _LibrarySettingsService( { "folder_paths": {"loras": [readable]}, "default_lora_root": readable, } ) monkeypatch.setattr(settings_manager_module, "settings", fake_settings) _stub_persistent_cache( monkeypatch, _FakePersistentCache(error=FileNotFoundError("no cache")) ) config_module.Config() persisted = fake_settings.payload["folder_paths"]["loras"] assert unknown.replace("\\", "/") in persisted def test_save_paths_keeps_the_root_a_default_setting_points_at( monkeypatch: pytest.MonkeyPatch, tmp_path ): """An empty unavailable default root must not be dropped. Dropping it would let the next boot "repair" the default onto another root and silently retarget downloads. ``default_unet_root`` is only known from the stored library (the sync resolves the lora/checkpoint/embedding ones itself), so this covers the fallback to the stored value. """ folder_paths = _setup_config_environment(monkeypatch, tmp_path) unet_dir = tmp_path / "unet" unet_dir.mkdir() default_target = str(tmp_path / "drive-Z" / "unet") folder_paths["unet"] = [str(unet_dir), default_target] fake_settings = _LibrarySettingsService( { "folder_paths": {"unet": [str(unet_dir)]}, "default_unet_root": default_target, } ) monkeypatch.setattr(settings_manager_module, "settings", fake_settings) _stub_persistent_cache(monkeypatch, _FakePersistentCache()) config_module.Config() persisted = fake_settings.payload["folder_paths"]["unet"] assert default_target.replace("\\", "/") in persisted assert str(unet_dir).replace("\\", "/") in persisted