Files
wehub-resource-sync cddb07a176
docs / deploy (push) Has been cancelled
docs / changes (push) Has been cancelled
docs / check-and-build (push) Has been cancelled
build container image / cpu (push) Has been cancelled
build container image / cuda (push) Has been cancelled
build container image / rocm (push) Has been cancelled
frontend checks / frontend-checks (push) Has been cancelled
frontend tests / frontend-tests (push) Has been cancelled
lfs checks / lfs-check (push) Has been cancelled
python checks / python-checks (push) Has been cancelled
python tests / py3.12: macos-default (push) Has been cancelled
python tests / py3.11: windows-cpu (push) Has been cancelled
python tests / py3.12: windows-cpu (push) Has been cancelled
python tests / py3.11: linux-cpu (push) Has been cancelled
typegen checks / typegen-checks (push) Has been cancelled
uv lock checks / uv-lock-checks (push) Has been cancelled
openapi checks / openapi-checks (push) Has been cancelled
python tests / py3.11: macos-default (push) Has been cancelled
python tests / py3.12: linux-cpu (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:22:06 +08:00

221 lines
8.6 KiB
Python

"""
Tests for missing model detection (_scan_for_missing_models) and bulk deletion.
"""
import gc
from pathlib import Path
import pytest
from invokeai.app.services.config import InvokeAIAppConfig
from invokeai.app.services.model_install import ModelInstallServiceBase
from invokeai.app.services.model_records import UnknownModelException
from invokeai.backend.model_manager.configs.textual_inversion import TI_File_SD1_Config
from invokeai.backend.model_manager.taxonomy import (
BaseModelType,
ModelFormat,
ModelSourceType,
ModelType,
)
from tests.backend.model_manager.model_manager_fixtures import * # noqa F403
class TestScanForMissingModels:
"""Tests for ModelInstallService._scan_for_missing_models()."""
def test_no_missing_models(
self, mm2_installer: ModelInstallServiceBase, embedding_file: Path, mm2_app_config: InvokeAIAppConfig
) -> None:
"""When all registered models exist on disk, _scan_for_missing_models returns an empty list."""
mm2_installer.register_path(embedding_file)
missing = mm2_installer._scan_for_missing_models()
assert len(missing) == 0
def test_detects_missing_model(
self, mm2_installer: ModelInstallServiceBase, embedding_file: Path, mm2_app_config: InvokeAIAppConfig
) -> None:
"""A model whose path does not exist on disk is reported as missing."""
# Register a real model first, then add a fake one with a non-existent path
mm2_installer.register_path(embedding_file)
fake_config = TI_File_SD1_Config(
key="missing-model-key-1",
path="/nonexistent/path/missing_model.safetensors",
name="MissingModel",
base=BaseModelType.StableDiffusion1,
type=ModelType.TextualInversion,
format=ModelFormat.EmbeddingFile,
hash="FAKEHASH1",
file_size=1024,
source="test/source",
source_type=ModelSourceType.Path,
)
mm2_installer.record_store.add_model(fake_config)
missing = mm2_installer._scan_for_missing_models()
assert len(missing) == 1
assert missing[0].key == "missing-model-key-1"
def test_mix_of_existing_and_missing(
self,
mm2_installer: ModelInstallServiceBase,
embedding_file: Path,
diffusers_dir: Path,
mm2_app_config: InvokeAIAppConfig,
) -> None:
"""With multiple models, only the ones with missing files are returned."""
key_existing = mm2_installer.register_path(embedding_file)
mm2_installer.register_path(diffusers_dir)
# Add two models with non-existent paths
fake1 = TI_File_SD1_Config(
key="missing-key-1",
path="/nonexistent/missing1.safetensors",
name="Missing1",
base=BaseModelType.StableDiffusion1,
type=ModelType.TextualInversion,
format=ModelFormat.EmbeddingFile,
hash="FAKEHASH_A",
file_size=1024,
source="test/source1",
source_type=ModelSourceType.Path,
)
fake2 = TI_File_SD1_Config(
key="missing-key-2",
path="/nonexistent/missing2.safetensors",
name="Missing2",
base=BaseModelType.StableDiffusion1,
type=ModelType.TextualInversion,
format=ModelFormat.EmbeddingFile,
hash="FAKEHASH_B",
file_size=2048,
source="test/source2",
source_type=ModelSourceType.Path,
)
mm2_installer.record_store.add_model(fake1)
mm2_installer.record_store.add_model(fake2)
missing = mm2_installer._scan_for_missing_models()
missing_keys = {m.key for m in missing}
assert len(missing) == 2
assert "missing-key-1" in missing_keys
assert "missing-key-2" in missing_keys
assert key_existing not in missing_keys
def test_empty_store_returns_empty(self, mm2_installer: ModelInstallServiceBase) -> None:
"""With no models registered, _scan_for_missing_models returns an empty list."""
missing = mm2_installer._scan_for_missing_models()
assert len(missing) == 0
class TestBulkDelete:
"""Tests for bulk model deletion."""
def test_delete_installed_model(
self, mm2_installer: ModelInstallServiceBase, embedding_file: Path, mm2_app_config: InvokeAIAppConfig
) -> None:
"""Deleting an installed model removes it from the store and disk."""
key = mm2_installer.install_path(embedding_file)
record = mm2_installer.record_store.get_model(key)
model_path = mm2_app_config.models_path / record.path
assert model_path.exists()
assert mm2_installer.record_store.exists(key)
gc.collect()
mm2_installer.delete(key)
with pytest.raises(UnknownModelException):
mm2_installer.record_store.get_model(key)
def test_unregister_missing_model(
self, mm2_installer: ModelInstallServiceBase, mm2_app_config: InvokeAIAppConfig
) -> None:
"""Unregistering a model whose file is missing removes it from the DB."""
fake_config = TI_File_SD1_Config(
key="missing-to-delete",
path="/nonexistent/path/gone.safetensors",
name="GoneModel",
base=BaseModelType.StableDiffusion1,
type=ModelType.TextualInversion,
format=ModelFormat.EmbeddingFile,
hash="FAKEHASH_GONE",
file_size=1024,
source="test/source",
source_type=ModelSourceType.Path,
)
mm2_installer.record_store.add_model(fake_config)
assert mm2_installer.record_store.exists("missing-to-delete")
# Unregister removes it from DB without touching disk
mm2_installer.unregister("missing-to-delete")
with pytest.raises(UnknownModelException):
mm2_installer.record_store.get_model("missing-to-delete")
def test_delete_unknown_key_raises(self, mm2_installer: ModelInstallServiceBase) -> None:
"""Deleting a model with an unknown key raises UnknownModelException."""
with pytest.raises(UnknownModelException):
mm2_installer.delete("nonexistent-key-12345")
def test_scan_then_unregister_clears_missing(
self, mm2_installer: ModelInstallServiceBase, mm2_app_config: InvokeAIAppConfig
) -> None:
"""After unregistering all missing models, _scan_for_missing_models returns empty."""
# Add two models with non-existent paths
for i in range(2):
config = TI_File_SD1_Config(
key=f"missing-bulk-{i}",
path=f"/nonexistent/bulk_{i}.safetensors",
name=f"BulkMissing{i}",
base=BaseModelType.StableDiffusion1,
type=ModelType.TextualInversion,
format=ModelFormat.EmbeddingFile,
hash=f"BULKHASH{i}",
file_size=1024,
source=f"test/bulk{i}",
source_type=ModelSourceType.Path,
)
mm2_installer.record_store.add_model(config)
missing = mm2_installer._scan_for_missing_models()
assert len(missing) == 2
# Unregister all missing (simulates bulk delete for missing models)
for model in missing:
mm2_installer.unregister(model.key)
assert len(mm2_installer._scan_for_missing_models()) == 0
def test_bulk_unregister_does_not_affect_existing_models(
self,
mm2_installer: ModelInstallServiceBase,
embedding_file: Path,
mm2_app_config: InvokeAIAppConfig,
) -> None:
"""Unregistering missing models does not affect models that exist on disk."""
existing_key = mm2_installer.register_path(embedding_file)
fake_config = TI_File_SD1_Config(
key="missing-selective",
path="/nonexistent/selective.safetensors",
name="SelectiveMissing",
base=BaseModelType.StableDiffusion1,
type=ModelType.TextualInversion,
format=ModelFormat.EmbeddingFile,
hash="SELECTIVEHASH",
file_size=1024,
source="test/selective",
source_type=ModelSourceType.Path,
)
mm2_installer.record_store.add_model(fake_config)
# Only unregister the missing one
missing = mm2_installer._scan_for_missing_models()
assert len(missing) == 1
for model in missing:
mm2_installer.unregister(model.key)
# Existing model should still be there
assert mm2_installer.record_store.exists(existing_key)
assert len(mm2_installer._scan_for_missing_models()) == 0