# SPDX-License-Identifier: AGPL-3.0-only # Copyright 2026-present the Unsloth AI Inc. team. All rights reserved. See /studio/LICENSE.AGPL-3.0 import importlib.machinery import importlib.util import sys import types from pathlib import Path import pytest _BACKEND_DIR = Path(__file__).resolve().parent.parent def _load_module( module_name: str, relative_path: str, monkeypatch = None, ): path = _BACKEND_DIR / relative_path spec = importlib.util.spec_from_file_location(module_name, path) assert spec is not None assert spec.loader is not None module = importlib.util.module_from_spec(spec) if monkeypatch is None: sys.modules[module_name] = module else: monkeypatch.setitem(sys.modules, module_name, module) spec.loader.exec_module(module) return module class _DummyLogger: def __getattr__(self, _name): return lambda *args, **kwargs: None class _Router: def get(self, *args, **kwargs): return lambda fn: fn def post(self, *args, **kwargs): return lambda fn: fn def delete(self, *args, **kwargs): return lambda fn: fn class _HTTPException(Exception): def __init__( self, status_code: int, detail: str | None = None, ): super().__init__(detail) self.status_code = status_code self.detail = detail class _LocalModelInfo: def __init__(self, **kwargs): self.__dict__.update(kwargs) def _identity_decorator(*_args, **_kwargs): return lambda fn: fn def _install_lightweight_backend_stubs(monkeypatch): fastapi = types.ModuleType("fastapi") fastapi.APIRouter = lambda: _Router() fastapi.Body = lambda default = None, **_kwargs: default fastapi.Depends = lambda dependency = None, **_kwargs: dependency fastapi.Header = lambda default = None, **_kwargs: default fastapi.HTTPException = _HTTPException fastapi.Query = lambda default = None, **_kwargs: default fastapi.Request = object monkeypatch.setitem(sys.modules, "fastapi", fastapi) fastapi_responses = types.ModuleType("fastapi.responses") fastapi_responses.StreamingResponse = object monkeypatch.setitem(sys.modules, "fastapi.responses", fastapi_responses) monkeypatch.setitem( sys.modules, "structlog", types.SimpleNamespace( BoundLogger = _DummyLogger, get_logger = lambda *args, **kwargs: _DummyLogger(), ), ) loggers = types.ModuleType("loggers") loggers.get_logger = lambda *args, **kwargs: _DummyLogger() monkeypatch.setitem(sys.modules, "loggers", loggers) auth_pkg = types.ModuleType("auth") auth_mod = types.ModuleType("auth.authentication") auth_mod.get_current_subject = lambda: None monkeypatch.setitem(sys.modules, "auth", auth_pkg) monkeypatch.setitem(sys.modules, "auth.authentication", auth_mod) core_pkg = types.ModuleType("core") core_export = types.ModuleType("core.export") core_export.get_export_backend = lambda: None core_inference = types.ModuleType("core.inference") core_inference.get_inference_backend = lambda: None monkeypatch.setitem(sys.modules, "core", core_pkg) monkeypatch.setitem(sys.modules, "core.export", core_export) monkeypatch.setitem(sys.modules, "core.inference", core_inference) utils_pkg = types.ModuleType("utils") utils_pkg.__path__ = [] utils_paths = types.ModuleType("utils.paths") storage_roots = _load_module( "utils.paths.storage_roots", "utils/paths/storage_roots.py", monkeypatch, ) utils_pkg.paths = utils_paths utils_paths.storage_roots = storage_roots utils_paths.is_local_path = lambda value: Path(str(value)).is_absolute() utils_paths.outputs_root = lambda: Path("outputs") utils_paths.exports_root = storage_roots.exports_root utils_paths.resolve_cached_repo_id_case = lambda value: value utils_paths.resolve_output_dir = lambda value = None: Path(value or "outputs") utils_paths.resolve_export_dir = storage_roots.resolve_export_dir monkeypatch.setitem(sys.modules, "utils", utils_pkg) monkeypatch.setitem(sys.modules, "utils.paths", utils_paths) utils_utils = types.ModuleType("utils.utils") utils_utils.log_and_http_error = lambda *args, **kwargs: (_ for _ in ()).throw( _HTTPException(kwargs.get("status_code", 500), kwargs.get("detail")) ) utils_utils.safe_error_detail = lambda value: str(value) monkeypatch.setitem(sys.modules, "utils.utils", utils_utils) utils_models = types.ModuleType("utils.models") for name in ( "scan_trained_models", "scan_exported_models", "scan_checkpoints", "list_gguf_variants", ): setattr(utils_models, name, lambda *args, **kwargs: []) for name in ( "get_base_model_from_checkpoint", "get_base_model_from_lora", "load_model_defaults", ): setattr(utils_models, name, lambda *args, **kwargs: None) utils_models.is_vision_model = lambda *args, **kwargs: False utils_models.is_embedding_model = lambda *args, **kwargs: False utils_models.ModelConfig = object monkeypatch.setitem(sys.modules, "utils.models", utils_models) utils_model_config = types.ModuleType("utils.models.model_config") utils_model_config._pick_best_gguf = lambda variants: variants[0] if variants else None utils_model_config._extract_quant_label = lambda value: value utils_model_config._is_big_endian_gguf_path = lambda *args, **kwargs: False utils_model_config.is_audio_input_type = lambda *args, **kwargs: None monkeypatch.setitem( sys.modules, "utils.models.model_config", utils_model_config, ) models_pkg = types.ModuleType("models") models_pkg.__path__ = [] for name in ( "CheckpointInfo", "CheckpointListResponse", "LocalModelListResponse", "ModelCheckpoints", "ModelDetails", "LoRAScanResponse", "LoRAInfo", "ModelListResponse", "LoadCheckpointRequest", "ExportStatusResponse", "ExportOperationResponse", "ExportMergedModelRequest", "ExportBaseModelRequest", "ExportGGUFRequest", "ExportLoRAAdapterRequest", ): setattr(models_pkg, name, object) models_pkg.LocalModelInfo = _LocalModelInfo monkeypatch.setitem(sys.modules, "models", models_pkg) models_models = types.ModuleType("models.models") for name in ( "BrowseEntry", "BrowseFoldersResponse", "ExportSizeResponse", "GgufVariantDetail", "GgufVariantsResponse", "ScanFolderInfo", "AddScanFolderRequest", ): setattr(models_models, name, object) models_models.ModelType = str monkeypatch.setitem(sys.modules, "models.models", models_models) models_responses = types.ModuleType("models.responses") for name in ( "LoRABaseModelResponse", "VisionCheckResponse", "EmbeddingCheckResponse", ): setattr(models_responses, name, object) monkeypatch.setitem(sys.modules, "models.responses", models_responses) def _install_pydantic_stub(monkeypatch): pydantic = types.ModuleType("pydantic") pydantic.BaseModel = object pydantic.Field = lambda default = None, **_kwargs: default pydantic.field_validator = _identity_decorator monkeypatch.setitem(sys.modules, "pydantic", pydantic) def _install_export_backend_stubs(monkeypatch): _install_lightweight_backend_stubs(monkeypatch) unsloth = types.ModuleType("unsloth") unsloth.FastLanguageModel = object unsloth.FastVisionModel = object unsloth._IS_MLX = True unsloth.__spec__ = importlib.machinery.ModuleSpec("unsloth", loader = None) monkeypatch.setitem(sys.modules, "unsloth", unsloth) unsloth_zoo = types.ModuleType("unsloth_zoo") unsloth_zoo.__path__ = [] unsloth_zoo.__spec__ = importlib.machinery.ModuleSpec( "unsloth_zoo", loader = None, is_package = True, ) llama_cpp = types.ModuleType("unsloth_zoo.llama_cpp") llama_cpp.LLAMA_CPP_DEFAULT_DIR = str(Path("/tmp/llama.cpp")) llama_cpp._resolve_local_convert_script = lambda *args, **kwargs: None llama_cpp.__spec__ = importlib.machinery.ModuleSpec( "unsloth_zoo.llama_cpp", loader = None, ) monkeypatch.setitem(sys.modules, "unsloth_zoo", unsloth_zoo) monkeypatch.setitem(sys.modules, "unsloth_zoo.llama_cpp", llama_cpp) huggingface_hub = types.ModuleType("huggingface_hub") huggingface_hub.HfApi = object huggingface_hub.ModelCard = object monkeypatch.setitem(sys.modules, "huggingface_hub", huggingface_hub) utils_hardware = types.ModuleType("utils.hardware") utils_hardware.clear_gpu_cache = lambda: None monkeypatch.setitem(sys.modules, "utils.hardware", utils_hardware) utils_models = sys.modules["utils.models"] utils_models.get_base_model_from_lora = lambda *args, **kwargs: None utils_models.is_vision_model = lambda *args, **kwargs: False utils_model_config = sys.modules["utils.models.model_config"] utils_model_config.detect_audio_type = lambda *args, **kwargs: None utils_paths = sys.modules["utils.paths"] utils_paths.ensure_dir = lambda path: Path(path).mkdir(parents = True, exist_ok = True) utils_paths.resolve_export_write_dir = lambda value = None: Path(value or "exports") utils_paths.resolve_output_dir = lambda value = None: Path(value or "outputs") def test_gguf_export_cleans_temp_dir_when_post_processing_fails(tmp_path, monkeypatch): _install_export_backend_stubs(monkeypatch) export_mod = _load_module("test_core_export_backend", "core/export/export.py", monkeypatch) cwd = tmp_path / "cwd" save_dir = tmp_path / "export" cwd.mkdir() monkeypatch.chdir(cwd) monkeypatch.setattr(export_mod, "resolve_export_write_dir", lambda _value: save_dir) monkeypatch.setattr( export_mod.shutil, "move", lambda *args, **kwargs: (_ for _ in ()).throw(OSError("move failed")), ) class _Model: def save_pretrained_gguf(self, model_save_path, tokenizer, quantization_method): Path(model_save_path).mkdir(parents = True) (Path(model_save_path) / "model.safetensors").write_bytes(b"weights") (cwd / "converted.gguf").write_bytes(b"gguf") backend = export_mod.ExportBackend.__new__(export_mod.ExportBackend) backend.current_model = _Model() backend.current_tokenizer = object() backend.current_checkpoint = None success, message, output_path = backend.export_gguf(str(save_dir), "Q4_K_M") assert success is False assert "move failed" in message assert output_path is None assert list(save_dir.glob("_tmp_model_*")) == [] def test_save_directory_validator_rejects_windows_parent_segments(monkeypatch): _install_pydantic_stub(monkeypatch) export_models = _load_module("test_models_export", "models/export.py", monkeypatch) with pytest.raises(ValueError, match = r"\.\."): export_models._validate_save_directory(r"E:\AI\..\secret") def test_save_directory_validator_allows_deep_absolute_paths(monkeypatch, tmp_path): _install_pydantic_stub(monkeypatch) export_models = _load_module("test_models_export_deep_path", "models/export.py", monkeypatch) deep_path = tmp_path for index in range(40): deep_path /= f"segment-{index:02d}" raw = str(deep_path) assert len(raw) > 255 assert export_models._validate_save_directory(raw) == raw def test_save_directory_validator_rejects_long_path_component(monkeypatch, tmp_path): _install_pydantic_stub(monkeypatch) export_models = _load_module( "test_models_export_long_component", "models/export.py", monkeypatch ) with pytest.raises(ValueError, match = "path components"): export_models._validate_save_directory(str(tmp_path / ("a" * 256))) def test_export_write_dir_accepts_external_absolute_but_read_dir_rejects(tmp_path, monkeypatch): storage_roots = _load_module( "test_storage_roots_accept_external", "utils/paths/storage_roots.py", ) export_root = tmp_path / "exports" external = tmp_path / "external" export_root.mkdir() external.mkdir() monkeypatch.setattr(storage_roots, "exports_root", lambda: export_root) assert storage_roots.resolve_export_write_dir(str(external)) == external with pytest.raises(ValueError, match = "path escapes root"): storage_roots.resolve_export_dir(str(external)) def test_export_write_dir_accepts_expanded_home_path(tmp_path, monkeypatch): storage_roots = _load_module( "test_storage_roots_accept_home_path", "utils/paths/storage_roots.py", ) export_root = tmp_path / "exports" home = tmp_path / "home" export_root.mkdir() home.mkdir() monkeypatch.setattr(storage_roots, "exports_root", lambda: export_root) if storage_roots.os.name == "nt": monkeypatch.setenv("USERPROFILE", str(home)) else: monkeypatch.setenv("HOME", str(home)) assert storage_roots.resolve_export_write_dir("~/exports/model") == home / "exports" / "model" def test_resolve_export_write_dir_rejects_backslash_parent_segment(): storage_roots = _load_module( "test_storage_roots_reject_parent", "utils/paths/storage_roots.py", ) with pytest.raises(ValueError, match = r"\.\."): storage_roots.resolve_export_write_dir(r"exports\..\outside") def test_export_write_dir_handles_non_native_windows_absolute_as_relative(tmp_path, monkeypatch): storage_roots = _load_module( "test_storage_roots_non_native_windows_path", "utils/paths/storage_roots.py", ) export_root = tmp_path / "exports" export_root.mkdir() monkeypatch.setattr(storage_roots, "exports_root", lambda: export_root) if storage_roots.os.name == "nt": pytest.skip("Windows drive paths are native on Windows") assert ( storage_roots.resolve_export_write_dir(r"C:\exports\model") == export_root / r"C:\exports\model" ) def test_export_details_registers_external_absolute_output(tmp_path, monkeypatch): _install_lightweight_backend_stubs(monkeypatch) export_route = _load_module( "test_routes_export_external", "routes/export.py", monkeypatch, ) output = tmp_path / "Gemma4_26B_gguf" output.mkdir() export_root = tmp_path / "studio" / "exports" export_root.mkdir(parents = True) registered = [] monkeypatch.setattr( export_route, "_try_register_external_export", lambda path: (registered.append(path) is None, str(path)), ) monkeypatch.setattr( "utils.paths.storage_roots.exports_root", lambda: export_root, ) details = export_route._export_details(str(output)) assert details == { "output_path": str(output), "scan_folder_registered": True, "scan_folder_path": str(output), } assert registered == [output] def test_export_details_does_not_register_contained_exports(tmp_path, monkeypatch): _install_lightweight_backend_stubs(monkeypatch) export_route = _load_module( "test_routes_export_contained", "routes/export.py", monkeypatch, ) export_root = tmp_path / "exports" output = export_root / "model-gguf" output.mkdir(parents = True) monkeypatch.setattr( export_route, "_try_register_external_export", lambda path: pytest.fail(f"unexpected registration: {path}"), ) monkeypatch.setattr( "utils.paths.storage_roots.exports_root", lambda: export_root, ) assert export_route._export_details(str(output)) == {"output_path": "model-gguf"} def test_registered_absolute_export_folder_is_discoverable(tmp_path, monkeypatch): _install_lightweight_backend_stubs(monkeypatch) models_route = _load_module("test_routes_models", "routes/models.py", monkeypatch) export_dir = tmp_path / "Gemma4_26B_gguf" export_dir.mkdir() gguf_file = export_dir / "Gemma4_26B.BF16-00001-of-00002.gguf" gguf_file.write_bytes(b"gguf") found = models_route._scan_models_dir(export_dir) assert len(found) == 1 assert found[0].path == str(gguf_file) assert found[0].source == "models_dir"