This commit is contained in:
2025-09-17 19:43:11 +02:00
parent 7c041e2a26
commit 0e9a655ea1
58 changed files with 2821 additions and 892 deletions

View File

@@ -0,0 +1,256 @@
"""
Comprehensive tests for import error handling in the text texture generator addon.
Tests conditional imports, PIL handling, mock operators, and version-specific features.
"""
import sys
import pytest
import tempfile
import zipfile
import os
from unittest.mock import patch, MagicMock, Mock
import importlib
class TestConditionalImports:
"""Test that modules handle missing imports gracefully."""
def test_operators_init_handles_missing_imports(self):
"""Test that operators/__init__.py sets up empty lists when modules missing."""
# Test operators module can import and has necessary attributes
try:
from src.operators import PRESET_OPERATORS, UTILITY_OPERATORS
# Should have lists (empty or populated)
assert isinstance(PRESET_OPERATORS, list)
assert isinstance(UTILITY_OPERATORS, list)
except ImportError as e:
pytest.fail(f"operators/__init__.py should import successfully: {e}")
def test_main_init_has_conditional_imports(self):
"""Test that main __init__.py has the conditional import structure."""
# Read the main __init__.py to verify it has conditional imports
from pathlib import Path
init_file = Path("src/__init__.py")
with open(init_file, 'r') as f:
content = f.read()
# Should have try-catch blocks for conditional imports
assert 'try:' in content
assert 'except ImportError' in content
assert 'MockOperator' in content
def test_core_init_has_conditional_imports(self):
"""Test that core/__init__.py has conditional import structure."""
# Test core module can import
try:
import src.core
# Should have the normal maps function available (real or mock)
assert hasattr(src.core, 'generate_normal_map_from_alpha')
assert callable(src.core.generate_normal_map_from_alpha)
# Should have the availability flag
assert hasattr(src.core, 'NORMAL_MAPS_AVAILABLE')
assert isinstance(src.core.NORMAL_MAPS_AVAILABLE, bool)
except ImportError as e:
pytest.fail(f"core/__init__.py should import successfully: {e}")
class TestPILImportHandling:
"""Test that PIL imports are handled gracefully when PIL is not available."""
def test_normal_maps_pil_handling(self):
"""Test that normal_maps.py has PIL handling structure."""
# Simply test that normal_maps can be imported
try:
from src.core import normal_maps
# Should have some indication of PIL handling
assert hasattr(normal_maps, 'PIL_AVAILABLE') or hasattr(normal_maps, 'generate_normal_map_from_alpha')
except ImportError as e:
pytest.fail(f"normal_maps.py should handle PIL gracefully: {e}")
def test_ui_preview_handles_missing_pil(self):
"""Test that ui/preview.py handles missing PIL gracefully."""
try:
from src.ui import preview
# Should import successfully with PIL handling
assert hasattr(preview, 'PIL_AVAILABLE')
assert isinstance(preview.PIL_AVAILABLE, bool)
except ImportError as e:
pytest.fail(f"ui/preview.py should handle missing PIL gracefully: {e}")
def test_generation_engine_imports_successfully(self):
"""Test that generation_engine.py imports successfully."""
try:
from src.core import generation_engine
# Should import successfully and have main functions
assert hasattr(generation_engine, 'generate_texture_image')
except ImportError as e:
pytest.fail(f"generation_engine.py should import successfully: {e}")
class TestMockOperators:
"""Test that mock operators work correctly in free version."""
def test_mock_operator_exists_in_main_init(self):
"""Test that MockOperator is defined in main __init__.py."""
# Read the main __init__.py to verify MockOperator is defined
from pathlib import Path
init_file = Path("src/__init__.py")
with open(init_file, 'r') as f:
content = f.read()
# Should have MockOperator class definition
assert 'class MockOperator' in content
assert 'bl_idname' in content
assert 'bl_label' in content
assert 'execute' in content
def test_mock_operators_structure_in_main_init(self):
"""Test that mock operators are properly structured in main __init__.py."""
# Read the main __init__.py to verify conditional operator assignments
from pathlib import Path
init_file = Path("src/__init__.py")
with open(init_file, 'r') as f:
content = f.read()
# Should have conditional operator assignments
operator_names = [
'TEXT_TEXTURE_OT_save_preset',
'TEXT_TEXTURE_OT_load_preset',
'TEXT_TEXTURE_OT_delete_preset'
]
for op_name in operator_names:
assert op_name in content, f"Should reference {op_name} in __init__.py"
class TestVersionSpecificConstants:
"""Test that version-specific constants are set correctly."""
def test_constants_has_enabled_features(self):
"""Test that constants.py includes ENABLED_FEATURES."""
from src.utils import constants
assert hasattr(constants, 'ENABLED_FEATURES')
assert isinstance(constants.ENABLED_FEATURES, list)
assert len(constants.ENABLED_FEATURES) > 0
def test_constants_has_version_type(self):
"""Test that constants.py includes VERSION_TYPE."""
from src.utils import constants
assert hasattr(constants, 'VERSION_TYPE')
assert constants.VERSION_TYPE in ['free', 'full']
def test_version_helper_functions(self):
"""Test that version helper functions work correctly."""
from src.utils import constants
assert hasattr(constants, 'is_free_version')
assert hasattr(constants, 'is_full_version')
assert hasattr(constants, 'get_version_limits')
# Functions should return appropriate types
assert isinstance(constants.is_free_version(), bool)
assert isinstance(constants.is_full_version(), bool)
assert isinstance(constants.get_version_limits(), dict)
class TestPackageIntegrity:
"""Test that built packages have correct structure and no import issues."""
def test_free_package_excludes_correct_files(self):
"""Test that free version package excludes the right files."""
from pathlib import Path
free_package = Path("dist/text_texture_generator_v1.0.0_free.zip")
if not free_package.exists():
pytest.skip("Free package not found - run build first")
with zipfile.ZipFile(free_package, 'r') as zip_file:
file_list = zip_file.namelist()
# Files that should be excluded from free version
excluded_files = [
'operators/preset_ops.py',
'operators/utility_ops.py',
'core/normal_maps.py'
]
for excluded_file in excluded_files:
matching_files = [f for f in file_list if excluded_file in f]
assert len(matching_files) == 0, f"Free version should not contain {excluded_file}"
def test_full_package_includes_all_files(self):
"""Test that full version package includes all files."""
from pathlib import Path
full_package = Path("dist/text_texture_generator_v1.0.0.zip")
if not full_package.exists():
pytest.skip("Full package not found - run build first")
with zipfile.ZipFile(full_package, 'r') as zip_file:
file_list = zip_file.namelist()
# Files that should be included in full version
required_files = [
'operators/preset_ops.py',
'operators/utility_ops.py',
'core/normal_maps.py'
]
for required_file in required_files:
matching_files = [f for f in file_list if required_file in f]
assert len(matching_files) > 0, f"Full version should contain {required_file}"
def test_constants_contains_enabled_features_in_packages(self):
"""Test that both packages have constants.py with ENABLED_FEATURES."""
from pathlib import Path
packages = [
("dist/text_texture_generator_v1.0.0_free.zip", "Free"),
("dist/text_texture_generator_v1.0.0.zip", "Full")
]
for package_path, package_type in packages:
package = Path(package_path)
if not package.exists():
continue # Skip if package doesn't exist
with zipfile.ZipFile(package, 'r') as zip_file:
file_list = zip_file.namelist()
constants_files = [f for f in file_list if 'utils/constants.py' in f]
assert len(constants_files) > 0, f"{package_type} package should contain constants.py"
# Read constants file and check for ENABLED_FEATURES
constants_file = constants_files[0]
constants_content = zip_file.read(constants_file).decode('utf-8')
assert 'ENABLED_FEATURES' in constants_content, f"{package_type} package constants.py should contain ENABLED_FEATURES"
class TestBuildSystemRobustness:
"""Test that the build system handles edge cases properly."""
def test_build_system_handles_missing_git_tags(self):
"""Test that build system works even without git tags."""
from build.sync_version import get_latest_git_tag
# Should return a version or None (both are acceptable)
version = get_latest_git_tag()
# Either a valid version string or None is acceptable
if version is not None:
assert isinstance(version, str)
assert len(version) > 0
def test_template_system_has_all_required_variables(self):
"""Test that template system includes all required variables."""
from build.sync_version import create_template_variables, load_config
config = load_config()
variables = create_template_variables(config, "1.0.0", "full")
required_vars = [
'VERSION',
'VERSION_TYPE',
'ENABLED_FEATURES'
]
for var in required_vars:
assert var in variables, f"Template variables should include {var}"

View File

@@ -0,0 +1,258 @@
"""
Comprehensive tests for version differentiation in the Blender text texture generator addon.
Tests the quadruple-layer detection system:
1. Template-generated constants (VERSION_TYPE, is_free_version(), ENABLED_FEATURES)
2. Runtime import detection flags (PRESETS_AVAILABLE, UTILITY_OPERATORS_AVAILABLE)
3. Version limits (get_version_limits() returns correct values)
4. UI helper functions (version display and feature gating)
"""
import pytest
import sys
from unittest.mock import Mock, patch, MagicMock
from types import ModuleType
class TestVersionDetection:
"""Test suite for version detection methods."""
def test_version_type_constant(self):
"""Test VERSION_TYPE constant is properly set."""
from src.utils.constants import VERSION_TYPE
assert VERSION_TYPE in ["free", "full"], f"VERSION_TYPE should be 'free' or 'full', got '{VERSION_TYPE}'"
def test_is_free_version_function(self):
"""Test is_free_version() function returns correct boolean."""
from src.utils.constants import is_free_version, VERSION_TYPE
result = is_free_version()
assert isinstance(result, bool), "is_free_version() should return a boolean"
# Result should match VERSION_TYPE
expected = VERSION_TYPE == "free"
assert result == expected, f"is_free_version() returned {result}, expected {expected} based on VERSION_TYPE='{VERSION_TYPE}'"
def test_is_full_version_function(self):
"""Test is_full_version() function returns correct boolean."""
from src.utils.constants import is_full_version, VERSION_TYPE
result = is_full_version()
assert isinstance(result, bool), "is_full_version() should return a boolean"
# Result should match VERSION_TYPE
expected = VERSION_TYPE == "full"
assert result == expected, f"is_full_version() returned {result}, expected {expected} based on VERSION_TYPE='{VERSION_TYPE}'"
def test_enabled_features_list(self):
"""Test ENABLED_FEATURES contains appropriate features for version."""
from src.utils.constants import ENABLED_FEATURES, VERSION_TYPE
assert isinstance(ENABLED_FEATURES, list), "ENABLED_FEATURES should be a list"
assert len(ENABLED_FEATURES) > 0, "ENABLED_FEATURES should not be empty"
# Basic features should always be present
assert "basic" in ENABLED_FEATURES, "Basic features should always be enabled"
assert "preview" in ENABLED_FEATURES, "Preview features should always be enabled"
if VERSION_TYPE == "free":
# Free version should have limited features - only basic and preview
expected_free_features = ["basic", "preview"]
for feature in ENABLED_FEATURES:
assert feature in expected_free_features, f"Free version should only have basic features, found '{feature}'"
else:
# Full version should have additional features beyond basic and preview
assert len(ENABLED_FEATURES) > 2, "Full version should have more than just basic and preview features"
def test_get_version_limits_function(self):
"""Test get_version_limits() returns correct technical limits."""
from src.utils.constants import get_version_limits, VERSION_TYPE
limits = get_version_limits()
assert isinstance(limits, dict), "get_version_limits() should return a dictionary"
# Check required keys
required_keys = ["max_texture_size", "max_concurrent_operations"]
for key in required_keys:
assert key in limits, f"Missing required key '{key}' in version limits"
# Check value types
assert isinstance(limits["max_texture_size"], int), "max_texture_size should be an integer"
assert isinstance(limits["max_concurrent_operations"], int), "max_concurrent_operations should be an integer"
# Check version-specific limits
if VERSION_TYPE == "free":
assert limits["max_texture_size"] == 1024, f"Free version should have 1024px limit, got {limits['max_texture_size']}"
assert limits["max_concurrent_operations"] == 1, f"Free version should have 1 concurrent operation, got {limits['max_concurrent_operations']}"
else:
assert limits["max_texture_size"] == 4096, f"Full version should have 4096px limit, got {limits['max_texture_size']}"
assert limits["max_concurrent_operations"] == 4, f"Full version should have 4 concurrent operations, got {limits['max_concurrent_operations']}"
class TestRuntimeImportDetection:
"""Test suite for runtime import detection flags."""
def test_presets_available_flag(self):
"""Test PRESETS_AVAILABLE flag is properly set based on import success."""
from src import PRESETS_AVAILABLE
assert isinstance(PRESETS_AVAILABLE, bool), "PRESETS_AVAILABLE should be a boolean"
def test_utility_operators_available_flag(self):
"""Test UTILITY_OPERATORS_AVAILABLE flag is properly set based on import success."""
from src import UTILITY_OPERATORS_AVAILABLE
assert isinstance(UTILITY_OPERATORS_AVAILABLE, bool), "UTILITY_OPERATORS_AVAILABLE should be a boolean"
class TestFeatureAvailability:
"""Test suite for feature availability and gating."""
def test_feature_gating_based_on_version(self):
"""Test that features are properly gated based on version type."""
from src.utils.constants import is_free_version, ENABLED_FEATURES
if is_free_version():
# Free version should only have basic and preview features
expected_features = ["basic", "preview"]
assert set(ENABLED_FEATURES) == set(expected_features), f"Free version should only have {expected_features}"
else:
# Full version should have more features
assert len(ENABLED_FEATURES) > 2, "Full version should have more than just basic features"
assert "basic" in ENABLED_FEATURES, "Full version should include basic features"
assert "preview" in ENABLED_FEATURES, "Full version should include preview features"
def test_runtime_import_detection_flags_exist(self):
"""Test that runtime import detection flags exist."""
try:
from src import PRESETS_AVAILABLE, UTILITY_OPERATORS_AVAILABLE
assert isinstance(PRESETS_AVAILABLE, bool), "PRESETS_AVAILABLE should be a boolean"
assert isinstance(UTILITY_OPERATORS_AVAILABLE, bool), "UTILITY_OPERATORS_AVAILABLE should be a boolean"
except ImportError as e:
pytest.fail(f"Runtime detection flags should be available: {e}")
class TestUIComponents:
"""Test suite for UI component version display."""
def test_get_version_badge_text(self):
"""Test get_version_badge_text() returns appropriate badge text."""
from src.utils.constants import get_version_badge_text, is_free_version
badge_text = get_version_badge_text()
assert isinstance(badge_text, str), "get_version_badge_text() should return a string"
assert len(badge_text) > 0, "Badge text should not be empty"
if is_free_version():
assert "FREE" in badge_text.upper(), "Free version badge should contain 'FREE'"
else:
assert "PRO" in badge_text.upper() or "FULL" in badge_text.upper(), "Full version badge should contain 'PRO' or 'FULL'"
def test_should_show_feature_lock(self):
"""Test should_show_feature_lock() returns correct values for features."""
from src.utils.constants import should_show_feature_lock, is_free_version
# Test premium features from the actual implementation
premium_features = ["presets", "utility_operators", "image_overlays", "batch_processing", "advanced_positioning"]
for feature in premium_features:
result = should_show_feature_lock(feature)
assert isinstance(result, bool), f"should_show_feature_lock('{feature}') should return boolean"
if is_free_version():
assert result == True, f"Feature lock should be shown for '{feature}' in free version"
else:
assert result == False, f"Feature lock should not be shown for '{feature}' in full version"
def test_get_upgrade_hint_text(self):
"""Test get_upgrade_hint_text() returns appropriate upgrade messages."""
from src.utils.constants import get_upgrade_hint_text, is_free_version
# The function requires a feature_name parameter
feature_name = "Test Feature"
hint_text = get_upgrade_hint_text(feature_name)
assert isinstance(hint_text, str), "get_upgrade_hint_text() should return a string"
if is_free_version():
assert len(hint_text) > 0, "Free version should have upgrade hint text"
assert feature_name in hint_text, "Upgrade hint should mention the feature name"
assert "Pro" in hint_text, "Upgrade hint should mention Pro version"
else:
assert len(hint_text) == 0, "Full version should have empty upgrade hint text"
def test_get_version_info_details(self):
"""Test get_version_info_details() returns comprehensive version information."""
from src.utils.constants import get_version_info_details, is_free_version, get_version_limits
info_details = get_version_info_details()
assert isinstance(info_details, dict), "get_version_info_details() should return a dictionary"
assert len(info_details) > 0, "Version info details should not be empty"
class TestIntegrationScenarios:
"""Test suite for cross-system integration validation."""
def test_template_and_runtime_consistency(self):
"""Test that template-generated constants and runtime flags are consistent."""
from src.utils.constants import is_free_version, ENABLED_FEATURES
from src import PRESETS_AVAILABLE, UTILITY_OPERATORS_AVAILABLE
# In a properly built system, runtime availability should align with template configuration
if is_free_version():
# Free version should have limited features in template
expected_features = ["basic", "preview"]
assert set(ENABLED_FEATURES) == set(expected_features), f"Template should only enable basic features in free version, got {ENABLED_FEATURES}"
else:
# Full version should have more features enabled in template
assert len(ENABLED_FEATURES) > 2, "Template should enable more features in full version"
assert "basic" in ENABLED_FEATURES, "Template should enable basic features in full version"
assert "preview" in ENABLED_FEATURES, "Template should enable preview features in full version"
def test_mock_operators_dont_crash(self):
"""Test that mock operators are properly implemented and don't crash."""
# This test ensures that when premium features are missing,
# the system gracefully handles it with mock implementations
# Test that we can import the main module without crashes
try:
import src
assert True, "Main module import should not crash"
except Exception as e:
pytest.fail(f"Main module import crashed: {e}")
# Test that runtime flags are properly set
assert hasattr(src, 'PRESETS_AVAILABLE'), "Runtime should set PRESETS_AVAILABLE flag"
assert hasattr(src, 'UTILITY_OPERATORS_AVAILABLE'), "Runtime should set UTILITY_OPERATORS_AVAILABLE flag"
class TestVersionDifferentiationScenarios:
"""Test specific free vs full version difference scenarios."""
def test_free_version_complete_scenario(self):
"""Test complete free version scenario with all restrictions."""
from src.utils.constants import is_free_version, get_version_limits, ENABLED_FEATURES
if is_free_version():
# Feature restrictions - free version should only have basic features
expected_features = ["basic", "preview"]
assert set(ENABLED_FEATURES) == set(expected_features), f"Free version should only have {expected_features}"
# Technical limitations
limits = get_version_limits()
assert limits["max_texture_size"] == 1024, "Should have 1024px texture limit"
assert limits["max_concurrent_operations"] == 1, "Should have 1 concurrent operation limit"
def test_full_version_complete_scenario(self):
"""Test complete full version scenario with all features enabled."""
from src.utils.constants import is_free_version, is_full_version, get_version_limits, ENABLED_FEATURES
if not is_free_version():
# Version detection
assert is_full_version() == True, "Should detect full version"
# Full version should have more features than free
assert len(ENABLED_FEATURES) > 2, "Full version should have more than just basic features"
assert "basic" in ENABLED_FEATURES, "Full version should include basic features"
assert "preview" in ENABLED_FEATURES, "Full version should include preview features"
# Generous technical limits
limits = get_version_limits()
assert limits["max_texture_size"] == 4096, "Should have 4096px texture limit"
assert limits["max_concurrent_operations"] == 4, "Should have 4 concurrent operations limit"