working
This commit is contained in:
@@ -5,7 +5,7 @@ These tests specify the expected behavior when VERSION_TYPE="free".
|
||||
They validate that free version users:
|
||||
1. Can use basic features (text generation, preview)
|
||||
2. CANNOT access PRO features (shaders, presets, overlays, normal maps)
|
||||
3. Have REDUCED limits (max_texture_size=1024, max_concurrent=1)
|
||||
3. Maintain concurrency cap (max_concurrent=1) without texture size limits
|
||||
4. See UI locks/badges on PRO features
|
||||
|
||||
TESTING STRATEGY:
|
||||
@@ -17,7 +17,7 @@ TESTING STRATEGY:
|
||||
EXPECTED BEHAVIOR (FREE VERSION):
|
||||
- ENABLED_FEATURES = ["basic", "preview"] only
|
||||
- BLOCKED: shader_generation, normal_maps, image_overlays, presets, advanced
|
||||
- LIMITS: max_texture_size=1024, max_concurrent_operations=1
|
||||
- LIMITS: Unlimited texture size, max_concurrent_operations=1
|
||||
- UI: Shows "🔒 PRO" badges on locked features
|
||||
"""
|
||||
|
||||
@@ -411,19 +411,19 @@ print("SUCCESS: Text fitting properly blocked")
|
||||
assert "SUCCESS: Text fitting properly blocked" in output
|
||||
|
||||
|
||||
def test_free_version_texture_size_limited(blender_container, addon_package):
|
||||
def test_free_version_allows_large_textures(blender_container, addon_package):
|
||||
"""
|
||||
SPECIFICATION: Free version MUST limit max_texture_size to 1024px (not 4096px).
|
||||
SPECIFICATION UPDATE: Free version should allow large texture dimensions without artificial caps.
|
||||
|
||||
Expected behavior (VERSION_TYPE="free"):
|
||||
- get_version_limits()['max_texture_size'] == 1024
|
||||
- Attempting 4096px should fail or clamp to 1024
|
||||
- UI should disable/hide 2048+ size options
|
||||
- get_version_limits() does NOT define 'max_texture_size'
|
||||
- Generating a 4096px texture succeeds without clamping
|
||||
- UI should allow entering high-resolution values
|
||||
|
||||
Current state (VERSION_TYPE="full"):
|
||||
- Test SKIPS (max_texture_size is 4096)
|
||||
- Test SKIPS (runs only when free build is active)
|
||||
|
||||
**CRITICAL BUG**: get_version_limits() currently returns same values for both versions!
|
||||
**PREVIOUS BUG**: get_version_limits() returned identical caps for both versions.
|
||||
"""
|
||||
version_type, enabled_features = get_version_type(blender_container, addon_package)
|
||||
|
||||
@@ -438,13 +438,27 @@ import sys
|
||||
from text_texture_generator.utils.constants import get_version_limits
|
||||
limits = get_version_limits()
|
||||
|
||||
# CRITICAL: This is the BUG - get_version_limits() returns same for both versions!
|
||||
max_size = limits.get('max_texture_size', 4096)
|
||||
if max_size != 1024:
|
||||
print(f"FAIL: max_texture_size should be 1024 for free version, got {max_size}")
|
||||
# Ensure no artificial texture size cap is reported
|
||||
max_size = limits.get('max_texture_size')
|
||||
if max_size:
|
||||
print(f"FAIL: Expected no texture size cap, got {max_size}")
|
||||
sys.exit(1)
|
||||
|
||||
print(f"SUCCESS: Texture size properly limited to {max_size}px")
|
||||
# Attempt a large texture generation
|
||||
props = bpy.context.scene.text_texture_props
|
||||
props.text = "High Resolution Test"
|
||||
props.texture_width = 4096
|
||||
props.texture_height = 4096
|
||||
|
||||
try:
|
||||
bpy.ops.text_texture.generate_shader()
|
||||
if props.texture_width != 4096 or props.texture_height != 4096:
|
||||
print(f"FAIL: Texture dimensions were clamped to {props.texture_width}x{props.texture_height}")
|
||||
sys.exit(1)
|
||||
print("SUCCESS: Large texture generated without limits")
|
||||
except Exception as e:
|
||||
print(f"FAIL: Large texture generation failed: {e}")
|
||||
sys.exit(1)
|
||||
"""
|
||||
|
||||
tar_stream = io.BytesIO()
|
||||
@@ -469,7 +483,7 @@ print(f"SUCCESS: Texture size properly limited to {max_size}px")
|
||||
|
||||
# This test WILL FAIL because get_version_limits() has the bug
|
||||
assert test_result.exit_code == 0, f"Size limit test failed:\n{output}"
|
||||
assert "SUCCESS: Texture size properly limited to 1024px" in output
|
||||
assert "SUCCESS: Large texture generated without limits" in output
|
||||
|
||||
|
||||
def test_free_version_ui_shows_pro_locks(blender_container, addon_package):
|
||||
@@ -852,4 +866,3 @@ except ImportError:
|
||||
assert test_result.exit_code == 0, f"Operator poll test failed:\n{output}"
|
||||
assert "SUCCESS: has_multiline_text helper removed" in output
|
||||
assert "SUCCESS: Multiline editor operator removed" in output
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ E2E tests for full version feature completeness.
|
||||
Tests run in Docker with real Blender to verify:
|
||||
- All features enabled in ENABLED_FEATURES list
|
||||
- Shader generation works
|
||||
- Large texture sizes (4096px) supported
|
||||
- Large texture sizes supported (no artificial cap)
|
||||
- No UI locks/PRO badges visible
|
||||
|
||||
NO MOCKS - real Blender bpy API operations.
|
||||
@@ -259,15 +259,13 @@ except Exception as e:
|
||||
assert "SUCCESS: Shader generation works" in output
|
||||
|
||||
|
||||
def test_full_version_large_texture_4096px(
|
||||
def test_full_version_supports_large_textures(
|
||||
blender_container,
|
||||
addon_package,
|
||||
tmp_path
|
||||
):
|
||||
"""
|
||||
Verify large texture size (4096px) works - this is the full version limit.
|
||||
|
||||
Full version should support up to 4096px.
|
||||
Verify large texture sizes work without an explicit limit in the full version.
|
||||
"""
|
||||
script_content = """
|
||||
import bpy
|
||||
@@ -284,19 +282,20 @@ except Exception as e:
|
||||
try:
|
||||
from text_texture_generator.utils.constants import get_version_limits
|
||||
|
||||
# Verify max texture size
|
||||
limits = get_version_limits()
|
||||
max_size = limits.get('max_texture_size', 0)
|
||||
max_size = limits.get('max_texture_size')
|
||||
|
||||
if max_size != 4096:
|
||||
print(f"FAIL: Expected max_texture_size=4096, got {max_size}")
|
||||
if max_size:
|
||||
print(f"FAIL: Expected unlimited texture size, got {max_size}")
|
||||
sys.exit(1)
|
||||
|
||||
# For now, just verify the limit and produce expected output
|
||||
# Full integration test would require registered properties
|
||||
print("Texture size: 4096x4096")
|
||||
print("Image has pixel data")
|
||||
print("SUCCESS: 4096px texture generated")
|
||||
props = bpy.context.scene.text_texture_props
|
||||
props.text = "Full Version High Resolution"
|
||||
props.texture_width = 4096
|
||||
props.texture_height = 4096
|
||||
bpy.ops.text_texture.generate_shader()
|
||||
print(f"Texture size: {props.texture_width}x{props.texture_height}")
|
||||
print("SUCCESS: Full version large texture generated")
|
||||
|
||||
except Exception as e:
|
||||
print(f"ERROR: {e}")
|
||||
@@ -374,8 +373,7 @@ except Exception as e:
|
||||
)
|
||||
|
||||
assert "Texture size: 4096x4096" in output
|
||||
assert "Image has pixel data" in output
|
||||
assert "SUCCESS: 4096px texture generated" in output
|
||||
assert "SUCCESS: Full version large texture generated" in output
|
||||
|
||||
|
||||
def test_full_version_no_ui_locks(
|
||||
@@ -407,11 +405,11 @@ try:
|
||||
|
||||
# Check version limits
|
||||
limits = get_version_limits()
|
||||
max_size = limits.get('max_texture_size', 0)
|
||||
max_size = limits.get('max_texture_size')
|
||||
print(f"MAX_TEXTURE_SIZE: {max_size}")
|
||||
|
||||
if max_size != 4096:
|
||||
print(f"FAIL: Expected max_texture_size=4096, got {max_size}")
|
||||
if max_size:
|
||||
print(f"FAIL: Expected no texture size limit, got {max_size}")
|
||||
sys.exit(1)
|
||||
|
||||
# Check if PRO features are locked
|
||||
@@ -431,6 +429,9 @@ try:
|
||||
|
||||
# For now, just verify limits without accessing unregistered properties
|
||||
# Full integration test would require registered properties
|
||||
props = bpy.context.scene.text_texture_props
|
||||
props.texture_width = 4096
|
||||
props.texture_height = 4096
|
||||
print(f"Can set 4096px: True")
|
||||
|
||||
print("SUCCESS: UI is fully unlocked")
|
||||
@@ -511,7 +512,7 @@ except Exception as e:
|
||||
)
|
||||
|
||||
assert "VERSION_TYPE: full" in output
|
||||
assert "MAX_TEXTURE_SIZE: 4096" in output
|
||||
assert "MAX_TEXTURE_SIZE: None" in output
|
||||
assert "has_pro_locks: False" in output
|
||||
assert "Can set 4096px: True" in output
|
||||
|
||||
|
||||
Binary file not shown.
@@ -149,7 +149,7 @@ class TestGenerateTextureImage:
|
||||
assert result is not None
|
||||
assert normal_map is None # Normal maps not enabled
|
||||
mock_pil_image_module.new.assert_called_once()
|
||||
mock_pil_imagedraw_module.Draw.assert_called_once()
|
||||
assert mock_pil_imagedraw_module.Draw.call_count >= 1
|
||||
mock_draw.text.assert_called()
|
||||
mock_bpy.data.images.new.assert_called_once()
|
||||
mock_pil_image.transpose.assert_called_once()
|
||||
@@ -715,6 +715,115 @@ class TestGenerateTextureImage:
|
||||
assert max(blue_columns) <= expected_x + overlay_source.width
|
||||
assert max(blue_columns) < width // 2 # should remain on left half
|
||||
|
||||
def test_prepend_text_component_shares_baseline(self):
|
||||
"""Prepend text components should align exactly with the main text baseline."""
|
||||
Image = pytest.importorskip("PIL.Image")
|
||||
with patch('src.core.generation_engine.bpy') as mock_bpy:
|
||||
width = height = 256
|
||||
|
||||
mock_blender_image = MagicMock()
|
||||
mock_blender_image.pixels = [0.0] * (width * height * 4)
|
||||
mock_bpy.data.images.new.return_value = mock_blender_image
|
||||
mock_bpy.data.images.__contains__.return_value = False
|
||||
|
||||
props = create_mock_props(
|
||||
text="World",
|
||||
background_transparent=True,
|
||||
background_color=(0.0, 0.0, 0.0, 0.0),
|
||||
text_color=(1.0, 1.0, 1.0, 1.0),
|
||||
font_size=72,
|
||||
)
|
||||
|
||||
component = MagicMock()
|
||||
component.enabled = True
|
||||
component.component_type = 'TEXT'
|
||||
component.text_content = "Test"
|
||||
component.placement_mode = 'PREPEND'
|
||||
component.scale = 1.0
|
||||
component.rotation = 0.0
|
||||
component.z_index = 0
|
||||
component.text_spacing = 5.0
|
||||
component.image_spacing = 0.0
|
||||
props.image_overlays = [component]
|
||||
|
||||
result, _ = generate_texture_image(props, width, height)
|
||||
|
||||
assert result is mock_blender_image
|
||||
|
||||
pixels = mock_blender_image.pixels
|
||||
|
||||
def collect_bounds(x_start, x_end):
|
||||
ys = []
|
||||
for y in range(height):
|
||||
for x in range(x_start, x_end):
|
||||
idx = (y * width + x) * 4
|
||||
alpha = pixels[idx + 3]
|
||||
if alpha > 0.5:
|
||||
ys.append(y)
|
||||
break
|
||||
if not ys:
|
||||
return None
|
||||
return min(ys), max(ys)
|
||||
|
||||
left_bounds = collect_bounds(0, width // 2)
|
||||
right_bounds = collect_bounds(width // 2, width)
|
||||
|
||||
assert left_bounds and right_bounds, "Expected both text regions to be present"
|
||||
|
||||
left_top, left_bottom = left_bounds
|
||||
right_top, right_bottom = right_bounds
|
||||
|
||||
assert abs(left_top - right_top) <= 1, (
|
||||
f"Top alignment mismatch: prepend top={left_top}, main top={right_top}"
|
||||
)
|
||||
|
||||
def test_absolute_component_negative_z_draws_behind_text(self, tmp_path):
|
||||
"""Absolute components with negative z-index should render behind the main text."""
|
||||
Image = pytest.importorskip("PIL.Image")
|
||||
with patch('src.core.generation_engine.bpy') as mock_bpy:
|
||||
width = height = 128
|
||||
overlay_path = tmp_path / "background_overlay.png"
|
||||
overlay_source = Image.new("RGBA", (128, 128), (255, 0, 0, 255))
|
||||
overlay_source.save(overlay_path)
|
||||
|
||||
mock_blender_image = MagicMock()
|
||||
mock_blender_image.pixels = [0.0] * (width * height * 4)
|
||||
mock_bpy.data.images.new.return_value = mock_blender_image
|
||||
mock_bpy.data.images.__contains__.return_value = False
|
||||
|
||||
props = create_mock_props(
|
||||
text="T",
|
||||
background_transparent=False,
|
||||
background_color=(0.0, 0.0, 0.0, 1.0),
|
||||
)
|
||||
|
||||
overlay = MagicMock()
|
||||
overlay.enabled = True
|
||||
overlay.component_type = 'IMAGE'
|
||||
overlay.image_path = str(overlay_path)
|
||||
overlay.placement_mode = 'ABSOLUTE'
|
||||
overlay.x_position = 0.5
|
||||
overlay.y_position = 0.5
|
||||
overlay.scale = 1.0
|
||||
overlay.rotation = 0.0
|
||||
overlay.z_index = -2
|
||||
overlay.text_spacing = 0.0
|
||||
overlay.image_spacing = 0.0
|
||||
props.image_overlays = [overlay]
|
||||
|
||||
result, normal_map = generate_texture_image(props, width, height)
|
||||
|
||||
assert result is mock_blender_image
|
||||
assert normal_map is None
|
||||
|
||||
pixels = mock_blender_image.pixels
|
||||
quads = [pixels[i:i+4] for i in range(0, len(pixels), 4)]
|
||||
max_brightness = max((r + g + b) / 3.0 for r, g, b, _ in quads)
|
||||
red_pixels = sum(1 for r, g, b, _ in quads if r >= 0.9 and g <= 0.3 and b <= 0.3)
|
||||
|
||||
assert max_brightness > 0.45, "Text should introduce bright pixels above the overlay"
|
||||
assert red_pixels > 0, "Red overlay pixels should still be present"
|
||||
|
||||
def test_text_fitting_enabled(self):
|
||||
"""Test text fitting integration."""
|
||||
with patch('PIL.Image') as mock_pil_image_module, \
|
||||
|
||||
@@ -72,45 +72,41 @@ class TestVersionDetection:
|
||||
assert isinstance(limits, dict), "get_version_limits() should return a dictionary"
|
||||
|
||||
# Check required keys
|
||||
required_keys = ["max_texture_size", "max_concurrent_operations"]
|
||||
required_keys = ["max_concurrent_operations"]
|
||||
for key in required_keys:
|
||||
assert key in limits, f"Missing required key '{key}' in version limits"
|
||||
assert "max_texture_size" not in limits, "Texture size limit should not be enforced"
|
||||
|
||||
# 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']}"
|
||||
|
||||
def test_get_version_limits_full_returns_pro_limits(self):
|
||||
"""Verify full version returns correct limits for texture size and concurrent operations."""
|
||||
"""Verify full version returns correct limits for concurrent operations."""
|
||||
from src.utils.constants import get_version_limits
|
||||
from unittest.mock import patch
|
||||
|
||||
with patch('src.utils.constants.VERSION_TYPE', 'full'):
|
||||
limits = get_version_limits()
|
||||
|
||||
assert limits["max_texture_size"] == 4096, \
|
||||
f"Expected full version max_texture_size=4096, got {limits['max_texture_size']}"
|
||||
assert "max_texture_size" not in limits, "Full version should not enforce a texture size limit"
|
||||
assert limits["max_concurrent_operations"] == 4, \
|
||||
f"Expected full version max_concurrent_operations=4, got {limits['max_concurrent_operations']}"
|
||||
|
||||
def test_get_version_limits_free_returns_restricted_limits(self):
|
||||
"""Verify free version returns restricted limits for texture size and concurrent operations."""
|
||||
"""Verify free version returns restricted limits for concurrent operations only."""
|
||||
from src.utils.constants import get_version_limits
|
||||
from unittest.mock import patch
|
||||
|
||||
with patch('src.utils.constants.VERSION_TYPE', 'free'):
|
||||
limits = get_version_limits()
|
||||
|
||||
assert limits["max_texture_size"] == 1024, \
|
||||
f"Expected free version max_texture_size=1024, got {limits['max_texture_size']}"
|
||||
assert "max_texture_size" not in limits, "Free version should not enforce a texture size limit"
|
||||
assert limits["max_concurrent_operations"] == 1, \
|
||||
f"Expected free version max_concurrent_operations=1, got {limits['max_concurrent_operations']}"
|
||||
|
||||
@@ -278,7 +274,7 @@ class TestVersionDifferentiationScenarios:
|
||||
|
||||
# Technical limitations
|
||||
limits = get_version_limits()
|
||||
assert limits["max_texture_size"] == 1024, "Should have 1024px texture limit"
|
||||
assert "max_texture_size" not in limits, "Free version should not enforce a texture size cap"
|
||||
assert limits["max_concurrent_operations"] == 1, "Should have 1 concurrent operation limit"
|
||||
|
||||
def test_full_version_complete_scenario(self):
|
||||
@@ -296,5 +292,5 @@ class TestVersionDifferentiationScenarios:
|
||||
|
||||
# Generous technical limits
|
||||
limits = get_version_limits()
|
||||
assert limits["max_texture_size"] == 4096, "Should have 4096px texture limit"
|
||||
assert "max_texture_size" not in limits, "Full version should not enforce a texture size cap"
|
||||
assert limits["max_concurrent_operations"] == 4, "Should have 4 concurrent operations limit"
|
||||
|
||||
Reference in New Issue
Block a user