""" E2E Tests for Free Version Feature Restrictions 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. Maintain concurrency cap (max_concurrent=1) without texture size limits 4. See UI locks/badges on PRO features TESTING STRATEGY: - Tests detect current VERSION_TYPE from running addon - If VERSION_TYPE="full": tests SKIP (documented as specification) - If VERSION_TYPE="free": tests RUN and validate restrictions - Tests use NO MOCKS - only real Blender bpy API operations EXPECTED BEHAVIOR (FREE VERSION): - ENABLED_FEATURES = ["basic", "preview"] only - BLOCKED: shader_generation, normal_maps, image_overlays, presets, advanced - LIMITS: Unlimited texture size, max_concurrent_operations=1 - UI: Shows "🔒 PRO" badges on locked features """ import json import pytest from pathlib import Path import tarfile import io pytestmark = pytest.mark.e2e def get_version_type(blender_container, addon_package): """Detect VERSION_TYPE from running addon.""" script_content = """ import bpy import sys import json # Enable addon try: bpy.ops.preferences.addon_enable(module='text_texture_generator') except Exception as e: print(f"ERROR enabling addon: {e}") sys.exit(1) # Import constants try: from text_texture_generator.utils.constants import VERSION_TYPE, ENABLED_FEATURES result = { 'version_type': VERSION_TYPE, 'enabled_features': ENABLED_FEATURES } print('RESULT:', json.dumps(result)) except Exception as e: print(f"ERROR importing constants: {e}") sys.exit(1) """ # Create tar with script tar_stream = io.BytesIO() with tarfile.open(fileobj=tar_stream, mode='w') as tar: script_info = tarfile.TarInfo(name='detect_version.py') script_bytes = script_content.encode('utf-8') script_info.size = len(script_bytes) tar.addfile(script_info, io.BytesIO(script_bytes)) tar_stream.seek(0) blender_container.put_archive('/addon-test', tar_stream) # Copy addon package addon_tar_stream = io.BytesIO() with tarfile.open(fileobj=addon_tar_stream, mode='w') as tar: addon_info = tarfile.TarInfo(name='text_texture_generator.zip') addon_info.size = addon_package.stat().st_size with open(addon_package, 'rb') as f: tar.addfile(addon_info, f) addon_tar_stream.seek(0) blender_container.put_archive('/addon-test', addon_tar_stream) # Install addon install_cmd = [ "/usr/local/bin/run_blender.sh", "--background", "--python-expr", ( "import bpy; " "bpy.ops.preferences.addon_install(filepath='/addon-test/text_texture_generator.zip'); " "bpy.ops.preferences.addon_enable(module='text_texture_generator'); " "bpy.ops.wm.save_userpref()" ) ] install_result = blender_container.exec_run(install_cmd, workdir="/addon-test") assert install_result.exit_code == 0, f"Addon install failed: {install_result.output.decode()}" # Run detection script test_cmd = [ "/usr/local/bin/run_blender.sh", "--background", "--python", "/addon-test/detect_version.py" ] test_result = blender_container.exec_run(test_cmd, workdir="/addon-test") output = test_result.output.decode('utf-8', errors='replace') # Parse version info for line in output.split('\n'): if 'RESULT:' in line: data = json.loads(line.split('RESULT:')[1].strip()) return data['version_type'], data['enabled_features'] pytest.fail(f"Could not detect VERSION_TYPE from addon output:\n{output}") def test_free_version_basic_features_work(blender_container, addon_package): """ SPECIFICATION: Free version MUST allow basic text-to-texture generation. Expected behavior (VERSION_TYPE="free"): - Text input works - Material creation works - Preview generation works - "basic" feature is enabled Current state (VERSION_TYPE="full"): - Test SKIPS (marked as specification for future) """ version_type, enabled_features = get_version_type(blender_container, addon_package) if version_type == "full": pytest.skip("Test requires VERSION_TYPE='free' - currently 'full' (specification test)") # Test basic generation works script_content = """ import bpy import sys # Addon already enabled by get_version_type # Verify basic feature enabled from text_texture_generator.utils.constants import ENABLED_FEATURES assert "basic" in ENABLED_FEATURES, f"'basic' not in ENABLED_FEATURES: {ENABLED_FEATURES}" # Create test object bpy.ops.mesh.primitive_cube_add() obj = bpy.context.active_object # Try basic generation props = bpy.context.scene.text_texture_props props.text = "Free Version Test" props.texture_width = 512 props.texture_height = 512 # Within free limit props.auto_assign_material = True try: bpy.ops.text_texture.generate_shader() mat = obj.active_material assert mat is not None, "Material not created" print("SUCCESS: Basic generation works in free version") except Exception as e: print(f"FAIL: Basic generation failed: {e}") sys.exit(1) """ tar_stream = io.BytesIO() with tarfile.open(fileobj=tar_stream, mode='w') as tar: script_info = tarfile.TarInfo(name='test_basic.py') script_bytes = script_content.encode('utf-8') script_info.size = len(script_bytes) tar.addfile(script_info, io.BytesIO(script_bytes)) tar_stream.seek(0) blender_container.put_archive('/addon-test', tar_stream) test_cmd = [ "/usr/local/bin/run_blender.sh", "--background", "--python", "/addon-test/test_basic.py" ] test_result = blender_container.exec_run(test_cmd, workdir="/addon-test") output = test_result.output.decode('utf-8', errors='replace') assert test_result.exit_code == 0, f"Basic generation failed:\n{output}" assert "SUCCESS: Basic generation works" in output def test_free_version_blocks_shader_generation(blender_container, addon_package): """ SPECIFICATION: Free version MUST block shader generation feature. Expected behavior (VERSION_TYPE="free"): - "shader_generation" NOT in ENABLED_FEATURES - Shader properties unavailable or restricted - UI shows "🔒 PRO" badge Current state (VERSION_TYPE="full"): - Test SKIPS (shader_generation is available) """ version_type, enabled_features = get_version_type(blender_container, addon_package) if version_type == "full": pytest.skip("Test requires VERSION_TYPE='free' - currently 'full' (specification test)") script_content = """ import bpy import sys # Verify shader_generation NOT enabled from text_texture_generator.utils.constants import ENABLED_FEATURES if "shader_generation" in ENABLED_FEATURES: print(f"FAIL: shader_generation should NOT be in ENABLED_FEATURES for free version") sys.exit(1) print("SUCCESS: Shader generation properly blocked") """ tar_stream = io.BytesIO() with tarfile.open(fileobj=tar_stream, mode='w') as tar: script_info = tarfile.TarInfo(name='test_shader.py') script_bytes = script_content.encode('utf-8') script_info.size = len(script_bytes) tar.addfile(script_info, io.BytesIO(script_bytes)) tar_stream.seek(0) blender_container.put_archive('/addon-test', tar_stream) test_cmd = [ "/usr/local/bin/run_blender.sh", "--background", "--python", "/addon-test/test_shader.py" ] test_result = blender_container.exec_run(test_cmd, workdir="/addon-test") output = test_result.output.decode('utf-8', errors='replace') assert test_result.exit_code == 0, f"Shader block test failed:\n{output}" assert "SUCCESS: Shader generation properly blocked" in output def test_free_version_blocks_presets(blender_container, addon_package): """ SPECIFICATION: Free version MUST block preset save/load functionality. Expected behavior (VERSION_TYPE="free"): - "presets" NOT in ENABLED_FEATURES - Preset operators unavailable - UI shows "🔒 PRO" on preset buttons Current state (VERSION_TYPE="full"): - Test SKIPS (presets are available) """ version_type, enabled_features = get_version_type(blender_container, addon_package) if version_type == "full": pytest.skip("Test requires VERSION_TYPE='free' - currently 'full' (specification test)") script_content = """ import bpy import sys # Verify presets NOT enabled from text_texture_generator.utils.constants import ENABLED_FEATURES if "presets" in ENABLED_FEATURES: print(f"FAIL: presets should NOT be in ENABLED_FEATURES for free version") sys.exit(1) print("SUCCESS: Preset functionality properly blocked") """ tar_stream = io.BytesIO() with tarfile.open(fileobj=tar_stream, mode='w') as tar: script_info = tarfile.TarInfo(name='test_presets.py') script_bytes = script_content.encode('utf-8') script_info.size = len(script_bytes) tar.addfile(script_info, io.BytesIO(script_bytes)) tar_stream.seek(0) blender_container.put_archive('/addon-test', tar_stream) test_cmd = [ "/usr/local/bin/run_blender.sh", "--background", "--python", "/addon-test/test_presets.py" ] test_result = blender_container.exec_run(test_cmd, workdir="/addon-test") output = test_result.output.decode('utf-8', errors='replace') assert test_result.exit_code == 0, f"Preset block test failed:\n{output}" assert "SUCCESS: Preset functionality properly blocked" in output def test_free_version_blocks_image_overlays(blender_container, addon_package): """ SPECIFICATION: Free version MUST block image overlay functionality. Expected behavior (VERSION_TYPE="free"): - "image_overlays" NOT in ENABLED_FEATURES - Overlay controls unavailable - UI shows "🔒 PRO" on overlay controls Current state (VERSION_TYPE="full"): - Test SKIPS (overlays are available) """ version_type, enabled_features = get_version_type(blender_container, addon_package) if version_type == "full": pytest.skip("Test requires VERSION_TYPE='free' - currently 'full' (specification test)") script_content = """ import bpy import sys # Verify image_overlays NOT enabled from text_texture_generator.utils.constants import ENABLED_FEATURES if "image_overlays" in ENABLED_FEATURES: print(f"FAIL: image_overlays should NOT be in ENABLED_FEATURES for free version") sys.exit(1) print("SUCCESS: Image overlay properly blocked") """ tar_stream = io.BytesIO() with tarfile.open(fileobj=tar_stream, mode='w') as tar: script_info = tarfile.TarInfo(name='test_overlays.py') script_bytes = script_content.encode('utf-8') script_info.size = len(script_bytes) tar.addfile(script_info, io.BytesIO(script_bytes)) tar_stream.seek(0) blender_container.put_archive('/addon-test', tar_stream) test_cmd = [ "/usr/local/bin/run_blender.sh", "--background", "--python", "/addon-test/test_overlays.py" ] test_result = blender_container.exec_run(test_cmd, workdir="/addon-test") output = test_result.output.decode('utf-8', errors='replace') assert test_result.exit_code == 0, f"Overlay block test failed:\n{output}" def test_free_version_blocks_text_fitting(blender_container, addon_package): """ SPECIFICATION: Free version MUST block text fitting functionality. Expected behavior (VERSION_TYPE="free"): - "text_fitting" NOT in ENABLED_FEATURES - has_text_fitting() returns False - Text fitting controls unavailable - UI shows "🔒 PRO" on text fitting controls Current state (VERSION_TYPE="full"): - Test SKIPS (text_fitting is available) """ version_type, enabled_features = get_version_type(blender_container, addon_package) if version_type == "full": pytest.skip("Test requires VERSION_TYPE='free' - currently 'full' (specification test)") script_content = """ import bpy import sys # Verify text_fitting NOT enabled from text_texture_generator.utils.constants import ENABLED_FEATURES, has_text_fitting if "text_fitting" in ENABLED_FEATURES: print(f"FAIL: text_fitting should NOT be in ENABLED_FEATURES for free version") sys.exit(1) # Verify has_text_fitting() returns False if has_text_fitting(): print(f"FAIL: has_text_fitting() should return False for free version") sys.exit(1) print("SUCCESS: Text fitting properly blocked") """ tar_stream = io.BytesIO() with tarfile.open(fileobj=tar_stream, mode='w') as tar: script_info = tarfile.TarInfo(name='test_text_fitting.py') script_bytes = script_content.encode('utf-8') script_info.size = len(script_bytes) tar.addfile(script_info, io.BytesIO(script_bytes)) tar_stream.seek(0) blender_container.put_archive('/addon-test', tar_stream) test_cmd = [ "/usr/local/bin/run_blender.sh", "--background", "--python", "/addon-test/test_text_fitting.py" ] test_result = blender_container.exec_run(test_cmd, workdir="/addon-test") output = test_result.output.decode('utf-8', errors='replace') assert test_result.exit_code == 0, f"Text fitting block test failed:\n{output}" assert "SUCCESS: Text fitting properly blocked" in output def test_free_version_allows_large_textures(blender_container, addon_package): """ SPECIFICATION UPDATE: Free version should allow large texture dimensions without artificial caps. Expected behavior (VERSION_TYPE="free"): - 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 (runs only when free build is active) **PREVIOUS BUG**: get_version_limits() returned identical caps for both versions. """ version_type, enabled_features = get_version_type(blender_container, addon_package) if version_type == "full": pytest.skip("Test requires VERSION_TYPE='free' - currently 'full' (specification test)") script_content = """ import bpy import sys # Check version limits from text_texture_generator.utils.constants import get_version_limits limits = get_version_limits() # 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) # 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() with tarfile.open(fileobj=tar_stream, mode='w') as tar: script_info = tarfile.TarInfo(name='test_size.py') script_bytes = script_content.encode('utf-8') script_info.size = len(script_bytes) tar.addfile(script_info, io.BytesIO(script_bytes)) tar_stream.seek(0) blender_container.put_archive('/addon-test', tar_stream) test_cmd = [ "/usr/local/bin/run_blender.sh", "--background", "--python", "/addon-test/test_size.py" ] test_result = blender_container.exec_run(test_cmd, workdir="/addon-test") output = test_result.output.decode('utf-8', errors='replace') # 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: Large texture generated without limits" in output def test_free_version_ui_shows_pro_locks(blender_container, addon_package): """ SPECIFICATION: Free version UI MUST show correct feature flags. Expected behavior (VERSION_TYPE="free"): - has_feature() returns False for locked features - has_feature() returns True for basic/preview only - UI can check these flags to show "🔒 PRO" badges Current state (VERSION_TYPE="full"): - Test SKIPS (all features unlocked) """ version_type, enabled_features = get_version_type(blender_container, addon_package) if version_type == "full": pytest.skip("Test requires VERSION_TYPE='free' - currently 'full' (specification test)") script_content = """ import bpy import sys # Check feature flags from text_texture_generator.utils.constants import has_feature # Verify locked features return False locked_features = ["shader_generation", "presets", "image_overlays", "normal_maps"] failed = [] for feature in locked_features: if has_feature(feature): failed.append(feature) if failed: print(f"FAIL: These features should be locked but aren't: {failed}") sys.exit(1) # Verify enabled features return True enabled_features = ["basic", "preview"] not_enabled = [] for feature in enabled_features: if not has_feature(feature): not_enabled.append(feature) if not_enabled: print(f"FAIL: These features should be enabled but aren't: {not_enabled}") sys.exit(1) print("SUCCESS: Feature flags correctly configured for free version") """ tar_stream = io.BytesIO() with tarfile.open(fileobj=tar_stream, mode='w') as tar: script_info = tarfile.TarInfo(name='test_flags.py') script_bytes = script_content.encode('utf-8') script_info.size = len(script_bytes) tar.addfile(script_info, io.BytesIO(script_bytes)) tar_stream.seek(0) blender_container.put_archive('/addon-test', tar_stream) test_cmd = [ "/usr/local/bin/run_blender.sh", "--background", "--python", "/addon-test/test_flags.py" ] test_result = blender_container.exec_run(test_cmd, workdir="/addon-test") output = test_result.output.decode('utf-8', errors='replace') assert test_result.exit_code == 0, f"Feature flags test failed:\n{output}" assert "SUCCESS: Feature flags correctly configured" in output def test_free_version_concurrent_operations_limited(blender_container, addon_package): """ SPECIFICATION: Free version MUST limit max_concurrent_operations to 1 (not 4). Expected behavior (VERSION_TYPE="free"): - get_version_limits()['max_concurrent_operations'] == 1 - Cannot queue multiple operations simultaneously Current state (VERSION_TYPE="full"): - Test SKIPS (max_concurrent is 4) **CRITICAL BUG**: get_version_limits() currently returns same values for both versions! """ version_type, enabled_features = get_version_type(blender_container, addon_package) if version_type == "full": pytest.skip("Test requires VERSION_TYPE='free' - currently 'full' (specification test)") script_content = """ import bpy import sys # Check version limits 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_concurrent = limits.get('max_concurrent_operations', 4) if max_concurrent != 1: print(f"FAIL: max_concurrent_operations should be 1 for free version, got {max_concurrent}") sys.exit(1) print(f"SUCCESS: max_concurrent_operations correctly limited to {max_concurrent}") """ tar_stream = io.BytesIO() with tarfile.open(fileobj=tar_stream, mode='w') as tar: script_info = tarfile.TarInfo(name='test_concurrent.py') script_bytes = script_content.encode('utf-8') script_info.size = len(script_bytes) tar.addfile(script_info, io.BytesIO(script_bytes)) tar_stream.seek(0) blender_container.put_archive('/addon-test', tar_stream) test_cmd = [ "/usr/local/bin/run_blender.sh", "--background", "--python", "/addon-test/test_concurrent.py" ] test_result = blender_container.exec_run(test_cmd, workdir="/addon-test") output = test_result.output.decode('utf-8', errors='replace') # This test WILL FAIL because get_version_limits() has the bug assert test_result.exit_code == 0, f"Concurrent limit test failed:\n{output}" assert "SUCCESS: max_concurrent_operations correctly limited to 1" in output def test_free_version_strips_newlines_from_multiline_text(blender_container, addon_package): """ SPECIFICATION: FREE version must strip newlines during text processing. Expected behavior (VERSION_TYPE="free"): - process_text_content() strips \\n from input text - "Line 1\\nLine 2\\nLine 3" becomes "Line 1 Line 2 Line 3" - Newlines replaced with spaces, consecutive spaces normalized Current state: - process_text_content() exists and works correctly (unit tested) - BUT: Not yet called in generation pipeline - Test WILL FAIL until integration is complete """ version_type, enabled_features = get_version_type(blender_container, addon_package) if version_type == "full": pytest.skip("Test requires VERSION_TYPE='free' - currently 'full' (specification test)") script_content = """ import bpy import sys # Test text processing directly from text_texture_generator.core.text_processor import process_text_content input_text = "Line 1\\nLine 2\\nLine 3" expected = "Line 1 Line 2 Line 3" result = process_text_content(input_text) if result != expected: print(f"FAIL: Expected '{expected}', got '{result}'") sys.exit(1) if "\\n" in result: print(f"FAIL: Result should not contain newlines, got: {result}") sys.exit(1) print("SUCCESS: Multiline text correctly stripped to single line") """ tar_stream = io.BytesIO() with tarfile.open(fileobj=tar_stream, mode='w') as tar: script_info = tarfile.TarInfo(name='test_newline_strip.py') script_bytes = script_content.encode('utf-8') script_info.size = len(script_bytes) tar.addfile(script_info, io.BytesIO(script_bytes)) tar_stream.seek(0) blender_container.put_archive('/addon-test', tar_stream) test_cmd = [ "/usr/local/bin/run_blender.sh", "--background", "--python", "/addon-test/test_newline_strip.py" ] test_result = blender_container.exec_run(test_cmd, workdir="/addon-test") output = test_result.output.decode('utf-8', errors='replace') assert test_result.exit_code == 0, f"Newline strip test failed:\n{output}" assert "SUCCESS: Multiline text correctly stripped" in output def test_free_version_handles_multiple_consecutive_newlines(blender_container, addon_package): """ SPECIFICATION: FREE version must collapse multiple consecutive newlines. Expected behavior (VERSION_TYPE="free"): - process_text_content() collapses multiple newlines - "Hello\\n\\n\\nWorld" becomes "Hello World" - Consecutive spaces normalized to single space Current state: - process_text_content() exists and works correctly (unit tested) - Test verifies the function behavior directly """ version_type, enabled_features = get_version_type(blender_container, addon_package) if version_type == "full": pytest.skip("Test requires VERSION_TYPE='free' - currently 'full' (specification test)") script_content = """ import bpy import sys from text_texture_generator.core.text_processor import process_text_content input_text = "Hello\\n\\n\\nWorld" expected = "Hello World" result = process_text_content(input_text) if result != expected: print(f"FAIL: Expected '{expected}', got '{result}'") sys.exit(1) if "\\n" in result: print(f"FAIL: Result should not contain newlines, got: {result}") sys.exit(1) print("SUCCESS: Multiple newlines correctly collapsed") """ tar_stream = io.BytesIO() with tarfile.open(fileobj=tar_stream, mode='w') as tar: script_info = tarfile.TarInfo(name='test_multi_newline.py') script_bytes = script_content.encode('utf-8') script_info.size = len(script_bytes) tar.addfile(script_info, io.BytesIO(script_bytes)) tar_stream.seek(0) blender_container.put_archive('/addon-test', tar_stream) test_cmd = [ "/usr/local/bin/run_blender.sh", "--background", "--python", "/addon-test/test_multi_newline.py" ] test_result = blender_container.exec_run(test_cmd, workdir="/addon-test") output = test_result.output.decode('utf-8', errors='replace') assert test_result.exit_code == 0, f"Multiple newline test failed:\n{output}" assert "SUCCESS: Multiple newlines correctly collapsed" in output def test_free_version_preserves_single_line_text(blender_container, addon_package): """ SPECIFICATION: FREE version should leave single-line text unchanged. Expected behavior (VERSION_TYPE="free"): - process_text_content() preserves text without newlines - "No newlines here" remains "No newlines here" - No modification when input is already single-line Current state: - process_text_content() exists and works correctly (unit tested) - Test verifies the function behavior directly """ version_type, enabled_features = get_version_type(blender_container, addon_package) if version_type == "full": pytest.skip("Test requires VERSION_TYPE='free' - currently 'full' (specification test)") script_content = """ import bpy import sys from text_texture_generator.core.text_processor import process_text_content input_text = "No newlines here" expected = "No newlines here" result = process_text_content(input_text) if result != expected: print(f"FAIL: Expected '{expected}', got '{result}'") sys.exit(1) if "\\n" in result: print(f"FAIL: Result should not contain newlines") sys.exit(1) print("SUCCESS: Single-line text remains unchanged") """ tar_stream = io.BytesIO() with tarfile.open(fileobj=tar_stream, mode='w') as tar: script_info = tarfile.TarInfo(name='test_single_line.py') script_bytes = script_content.encode('utf-8') script_info.size = len(script_bytes) tar.addfile(script_info, io.BytesIO(script_bytes)) tar_stream.seek(0) blender_container.put_archive('/addon-test', tar_stream) test_cmd = [ "/usr/local/bin/run_blender.sh", "--background", "--python", "/addon-test/test_single_line.py" ] test_result = blender_container.exec_run(test_cmd, workdir="/addon-test") output = test_result.output.decode('utf-8', errors='replace') assert test_result.exit_code == 0, f"Single-line test failed:\n{output}" def test_free_version_no_multiline_artifacts(blender_container, addon_package): """ SPECIFICATION: Multiline feature must be absent from the addon. This verification runs inside Blender to ensure that stale helpers or operators are not exposed through the package API. """ script_content = """ import sys def fail(msg): print(f"FAIL: {msg}") sys.exit(1) # has_multiline_text helper must be gone try: from text_texture_generator.utils.constants import has_multiline_text fail("has_multiline_text() helper is still importable") except ImportError: print("SUCCESS: has_multiline_text helper removed") # Legacy multiline operator must also be absent try: from text_texture_generator import TEXT_TEXTURE_OT_edit_multiline_text fail("TEXT_TEXTURE_OT_edit_multiline_text is still exposed") except ImportError: print("SUCCESS: Multiline editor operator removed") """ tar_stream = io.BytesIO() with tarfile.open(fileobj=tar_stream, mode='w') as tar: script_info = tarfile.TarInfo(name='test_editor_poll.py') script_bytes = script_content.encode('utf-8') script_info.size = len(script_bytes) tar.addfile(script_info, io.BytesIO(script_bytes)) tar_stream.seek(0) blender_container.put_archive('/addon-test', tar_stream) test_cmd = [ "/usr/local/bin/run_blender.sh", "--background", "--python", "/addon-test/test_editor_poll.py" ] test_result = blender_container.exec_run(test_cmd, workdir="/addon-test") output = test_result.output.decode('utf-8', errors='replace') 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