264 lines
8.3 KiB
Python
264 lines
8.3 KiB
Python
import json
|
|
import os
|
|
from pathlib import Path
|
|
from unittest.mock import Mock
|
|
|
|
import pytest
|
|
|
|
import src.presets.storage_system as storage_system
|
|
|
|
|
|
class MockPresetItem:
|
|
"""Simple container for preset collection entries."""
|
|
|
|
def __init__(self):
|
|
self.name = ""
|
|
self.source = ""
|
|
|
|
|
|
class MockPresetCollection:
|
|
"""Minimal stand-in for Blender's CollectionProperty."""
|
|
|
|
def __init__(self):
|
|
self._items = []
|
|
self.cleared = False
|
|
|
|
def clear(self):
|
|
self.cleared = True
|
|
self._items.clear()
|
|
|
|
def add(self):
|
|
item = MockPresetItem()
|
|
self._items.append(item)
|
|
return item
|
|
|
|
def remove(self, index):
|
|
self._items.pop(index)
|
|
|
|
def __iter__(self):
|
|
return iter(self._items)
|
|
|
|
def __len__(self):
|
|
return len(self._items)
|
|
|
|
|
|
class MockProps:
|
|
"""Mock text texture properties containing preset collection."""
|
|
|
|
def __init__(self):
|
|
self.presets = MockPresetCollection()
|
|
self.save_with_blend_file = False
|
|
self.new_preset_name = ""
|
|
self.active_preset_index = 0
|
|
self.preset_search_query = ""
|
|
self.last_loaded_preset = ""
|
|
|
|
|
|
class MockScene(dict):
|
|
"""Dictionary-like scene object supporting custom properties."""
|
|
|
|
def __init__(self, props=None):
|
|
super().__init__()
|
|
self.text_texture_props = props
|
|
|
|
def __bool__(self):
|
|
# Scene should be truthy even when no custom properties are set
|
|
return True
|
|
|
|
|
|
@pytest.fixture
|
|
def make_scene(mock_bpy_fixture):
|
|
"""Factory for assigning a mock scene to bpy.context."""
|
|
|
|
def _make_scene(props=None):
|
|
scene = MockScene(props)
|
|
mock_bpy_fixture.context.scene = scene
|
|
return scene
|
|
|
|
return _make_scene
|
|
|
|
|
|
def test_get_persistent_preset_path_creates_directory(tmp_path, mock_bpy_fixture):
|
|
config_dir = tmp_path / "config"
|
|
config_dir.mkdir()
|
|
mock_bpy_fixture.utils.user_resource.return_value = str(config_dir)
|
|
|
|
path = storage_system.get_persistent_preset_path()
|
|
expected = config_dir / "text_texture_generator" / "presets"
|
|
|
|
assert Path(path) == expected
|
|
assert expected.is_dir()
|
|
|
|
|
|
def test_save_preset_unified_persistent_writes_file(tmp_path, monkeypatch):
|
|
persistent_dir = tmp_path / "persistent"
|
|
persistent_dir.mkdir()
|
|
monkeypatch.setattr(storage_system, "get_persistent_preset_path", lambda: str(persistent_dir))
|
|
|
|
preset_data = {"text": "hello", "font_size": 24}
|
|
result = storage_system.save_preset_unified(preset_data, "SamplePreset", save_to_blend_file=False)
|
|
|
|
saved_path = persistent_dir / "SamplePreset.json"
|
|
assert result is True
|
|
assert saved_path.exists()
|
|
with saved_path.open("r") as handle:
|
|
assert json.load(handle) == preset_data
|
|
|
|
|
|
def test_save_preset_unified_blend_delegates(monkeypatch):
|
|
mock_save = Mock(return_value=True)
|
|
monkeypatch.setattr(storage_system, "save_blend_file_preset", mock_save)
|
|
|
|
payload = {"text": "blend storage"}
|
|
result = storage_system.save_preset_unified(payload, "BlendPreset", save_to_blend_file=True)
|
|
|
|
assert result is True
|
|
mock_save.assert_called_once_with("BlendPreset", payload)
|
|
|
|
|
|
def test_get_blend_file_presets_initializes_empty_collection(make_scene):
|
|
scene = make_scene()
|
|
|
|
presets = storage_system.get_blend_file_presets()
|
|
|
|
assert presets == {}
|
|
assert scene["text_texture_presets"] == "{}"
|
|
|
|
|
|
def test_save_blend_file_preset_stores_json_in_scene(make_scene):
|
|
scene = make_scene()
|
|
preset_content = {"text": "stored", "font_size": 18}
|
|
|
|
result = storage_system.save_blend_file_preset("StoredPreset", preset_content)
|
|
|
|
assert result is True
|
|
stored = json.loads(scene["text_texture_presets"])
|
|
assert stored["StoredPreset"] == preset_content
|
|
roundtrip = storage_system.get_blend_file_presets()
|
|
assert roundtrip["StoredPreset"] == preset_content
|
|
|
|
|
|
def test_delete_blend_file_preset_removes_entry(make_scene):
|
|
scene = make_scene()
|
|
storage_system.save_blend_file_preset("TempPreset", {"value": 1})
|
|
|
|
removed = storage_system.delete_blend_file_preset("TempPreset")
|
|
assert removed is True
|
|
assert json.loads(scene["text_texture_presets"]) == {}
|
|
|
|
removed_again = storage_system.delete_blend_file_preset("TempPreset")
|
|
assert removed_again is False
|
|
|
|
|
|
def test_get_unified_preset_list_prioritizes_blend_presets(tmp_path, monkeypatch):
|
|
persistent_dir = tmp_path / "persistent"
|
|
persistent_dir.mkdir()
|
|
monkeypatch.setattr(storage_system, "get_persistent_preset_path", lambda: str(persistent_dir))
|
|
|
|
with (persistent_dir / "alpha.json").open("w") as handle:
|
|
json.dump({"source": "persistent"}, handle)
|
|
with (persistent_dir / "shared.json").open("w") as handle:
|
|
json.dump({"source": "persistent-shared"}, handle)
|
|
|
|
monkeypatch.setattr(
|
|
storage_system,
|
|
"get_blend_file_presets",
|
|
lambda: {
|
|
"shared": {"source": "blend"},
|
|
"blend_only": {"source": "blend-only"},
|
|
},
|
|
)
|
|
|
|
presets = storage_system.get_unified_preset_list()
|
|
|
|
assert presets["alpha"][1] == "PERSISTENT_FILE"
|
|
assert presets["alpha"][0]["source"] == "persistent"
|
|
assert presets["shared"][1] == "BLEND_FILE"
|
|
assert presets["shared"][0]["source"] == "blend"
|
|
assert presets["blend_only"][1] == "BLEND_FILE"
|
|
|
|
|
|
def test_migrate_from_legacy_system_migrates_files_and_creates_backup(tmp_path, monkeypatch):
|
|
legacy_dir = tmp_path / "legacy"
|
|
persistent_dir = tmp_path / "persistent"
|
|
legacy_dir.mkdir()
|
|
persistent_dir.mkdir()
|
|
|
|
legacy_payload = {"example": True}
|
|
with (legacy_dir / "legacy_preset.json").open("w") as handle:
|
|
json.dump(legacy_payload, handle)
|
|
with (persistent_dir / "existing.json").open("w") as handle:
|
|
json.dump({"existing": True}, handle)
|
|
|
|
monkeypatch.setattr(storage_system, "get_preset_path", lambda: str(legacy_dir))
|
|
monkeypatch.setattr(storage_system, "get_persistent_preset_path", lambda: str(persistent_dir))
|
|
|
|
result = storage_system.migrate_from_legacy_system()
|
|
|
|
migrated_file = persistent_dir / "legacy_preset.json"
|
|
backup_file = persistent_dir / "legacy_backup" / "legacy_preset.json"
|
|
marker_file = persistent_dir / ".migration_complete"
|
|
|
|
assert result is True
|
|
assert migrated_file.exists()
|
|
assert json.loads(migrated_file.read_text()) == legacy_payload
|
|
assert backup_file.exists()
|
|
assert marker_file.exists()
|
|
|
|
|
|
def test_initialize_presets_populates_props_collection(make_scene, monkeypatch):
|
|
props = MockProps()
|
|
# Seed with existing entry to ensure clear() is invoked
|
|
props.presets.add().name = "stale"
|
|
migrate_mock = Mock(return_value=True)
|
|
monkeypatch.setattr(storage_system, "migrate_from_legacy_system", migrate_mock)
|
|
monkeypatch.setattr(
|
|
storage_system,
|
|
"get_unified_preset_list",
|
|
lambda: {
|
|
"BlendPreset": ({"value": "blend"}, "BLEND_FILE"),
|
|
"PersistentPreset": ({"value": "persistent"}, "PERSISTENT_FILE"),
|
|
},
|
|
)
|
|
|
|
scene = make_scene(props)
|
|
storage_system.initialize_presets()
|
|
|
|
assert migrate_mock.called
|
|
assert props.presets.cleared is True
|
|
assert len(props.presets) == 2
|
|
entries = {(item.name, item.source) for item in props.presets}
|
|
assert ("BlendPreset", "BLEND_FILE") in entries
|
|
assert ("PersistentPreset", "PERSISTENT_FILE") in entries
|
|
assert "text_texture_presets" not in scene # ensure scene custom data untouched
|
|
|
|
|
|
def test_ensure_presets_available_refreshes_when_empty(make_scene, monkeypatch):
|
|
props = MockProps()
|
|
make_scene(props)
|
|
refresh_mock = Mock(return_value=True)
|
|
monkeypatch.setattr(storage_system, "refresh_presets_sync", refresh_mock)
|
|
|
|
result = storage_system.ensure_presets_available()
|
|
|
|
assert result is True
|
|
refresh_mock.assert_called_once()
|
|
|
|
|
|
def test_ensure_presets_available_returns_true_when_presets_loaded(make_scene, monkeypatch):
|
|
props = MockProps()
|
|
props.presets.add().name = "cached"
|
|
make_scene(props)
|
|
refresh_mock = Mock(return_value=True)
|
|
monkeypatch.setattr(storage_system, "refresh_presets_sync", refresh_mock)
|
|
|
|
result = storage_system.ensure_presets_available()
|
|
|
|
assert result is True
|
|
refresh_mock.assert_not_called()
|
|
|
|
|
|
def test_refresh_presets_sync_returns_false_without_scene(mock_bpy_fixture):
|
|
mock_bpy_fixture.context.scene = None
|
|
assert storage_system.refresh_presets_sync() is False
|