Files
Text-Texture-Generator-for-…/tests/unit/presets/test_storage_system.py
2025-10-13 11:34:22 +02:00

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