mirror of
https://github.com/ChuckBuilds/LEDMatrix.git
synced 2026-05-15 18:03:32 +00:00
Remove unused imports across 86 files in src/, web_interface/, test/, and scripts/ using autoflake. No logic changes — only dead import statements and unused names in from-imports are removed. Also remove bare exception aliases where the variable is never referenced in the handler body: - src/cache/disk_cache.py: except (IOError, OSError, PermissionError) as e - src/cache_manager.py: except (OSError, IOError, PermissionError) as perm_error - src/plugin_system/resource_monitor.py: except Exception as e - web_interface/app.py: except Exception as read_err 86 files changed, 205 lines removed, 18 pre-existing test failures unchanged. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
306 lines
12 KiB
Python
306 lines
12 KiB
Python
"""
|
|
Base test class for plugin integration tests.
|
|
|
|
Provides common test functionality for all plugins.
|
|
"""
|
|
|
|
import pytest
|
|
import json
|
|
from typing import Dict, Any
|
|
|
|
from src.plugin_system.plugin_loader import PluginLoader
|
|
from src.plugin_system.base_plugin import BasePlugin
|
|
|
|
|
|
class PluginTestBase:
|
|
"""Base class for plugin integration tests."""
|
|
|
|
@pytest.fixture(autouse=True)
|
|
def setup_base(self, plugins_dir, mock_display_manager, mock_cache_manager,
|
|
mock_plugin_manager, base_plugin_config):
|
|
"""Setup base fixtures for all plugin tests."""
|
|
self.plugins_dir = plugins_dir
|
|
self.mock_display_manager = mock_display_manager
|
|
self.mock_cache_manager = mock_cache_manager
|
|
self.mock_plugin_manager = mock_plugin_manager
|
|
self.base_config = base_plugin_config
|
|
self.plugin_loader = PluginLoader()
|
|
|
|
def load_plugin_manifest(self, plugin_id: str) -> Dict[str, Any]:
|
|
"""Load plugin manifest.json."""
|
|
manifest_path = self.plugins_dir / plugin_id / 'manifest.json'
|
|
if not manifest_path.exists():
|
|
pytest.skip(f"Manifest not found for {plugin_id}")
|
|
|
|
with open(manifest_path, 'r') as f:
|
|
return json.load(f)
|
|
|
|
def load_plugin_config_schema(self, plugin_id: str) -> Dict[str, Any]:
|
|
"""Load plugin config_schema.json if it exists."""
|
|
schema_path = self.plugins_dir / plugin_id / 'config_schema.json'
|
|
if schema_path.exists():
|
|
with open(schema_path, 'r') as f:
|
|
return json.load(f)
|
|
return None
|
|
|
|
def test_manifest_exists(self, plugin_id: str):
|
|
"""Test that plugin manifest exists and is valid JSON."""
|
|
manifest = self.load_plugin_manifest(plugin_id)
|
|
assert manifest is not None
|
|
assert 'id' in manifest
|
|
assert manifest['id'] == plugin_id
|
|
assert 'class_name' in manifest
|
|
# entry_point is optional - default to 'manager.py' if missing
|
|
if 'entry_point' not in manifest:
|
|
manifest['entry_point'] = 'manager.py'
|
|
|
|
def test_manifest_has_required_fields(self, plugin_id: str):
|
|
"""Test that manifest has all required fields."""
|
|
manifest = self.load_plugin_manifest(plugin_id)
|
|
|
|
# Core required fields
|
|
required_fields = ['id', 'name', 'description', 'author', 'class_name']
|
|
for field in required_fields:
|
|
assert field in manifest, f"Manifest missing required field: {field}"
|
|
assert manifest[field], f"Manifest field {field} is empty"
|
|
|
|
# entry_point is required but some plugins may not have it explicitly
|
|
# If missing, assume it's 'manager.py'
|
|
if 'entry_point' not in manifest:
|
|
manifest['entry_point'] = 'manager.py'
|
|
|
|
def test_plugin_can_be_loaded(self, plugin_id: str):
|
|
"""Test that plugin module can be loaded."""
|
|
manifest = self.load_plugin_manifest(plugin_id)
|
|
plugin_dir = self.plugins_dir / plugin_id
|
|
entry_point = manifest.get('entry_point', 'manager.py')
|
|
|
|
module = self.plugin_loader.load_module(
|
|
plugin_id=plugin_id,
|
|
plugin_dir=plugin_dir,
|
|
entry_point=entry_point
|
|
)
|
|
|
|
assert module is not None
|
|
assert hasattr(module, manifest['class_name'])
|
|
|
|
def test_plugin_class_exists(self, plugin_id: str):
|
|
"""Test that plugin class exists in module."""
|
|
manifest = self.load_plugin_manifest(plugin_id)
|
|
plugin_dir = self.plugins_dir / plugin_id
|
|
entry_point = manifest.get('entry_point', 'manager.py')
|
|
class_name = manifest['class_name']
|
|
|
|
module = self.plugin_loader.load_module(
|
|
plugin_id=plugin_id,
|
|
plugin_dir=plugin_dir,
|
|
entry_point=entry_point
|
|
)
|
|
|
|
plugin_class = self.plugin_loader.get_plugin_class(
|
|
plugin_id=plugin_id,
|
|
module=module,
|
|
class_name=class_name
|
|
)
|
|
|
|
assert plugin_class is not None
|
|
assert issubclass(plugin_class, BasePlugin)
|
|
|
|
def test_plugin_can_be_instantiated(self, plugin_id: str):
|
|
"""Test that plugin can be instantiated with mock dependencies."""
|
|
manifest = self.load_plugin_manifest(plugin_id)
|
|
plugin_dir = self.plugins_dir / plugin_id
|
|
entry_point = manifest.get('entry_point', 'manager.py')
|
|
class_name = manifest['class_name']
|
|
|
|
module = self.plugin_loader.load_module(
|
|
plugin_id=plugin_id,
|
|
plugin_dir=plugin_dir,
|
|
entry_point=entry_point
|
|
)
|
|
|
|
plugin_class = self.plugin_loader.get_plugin_class(
|
|
plugin_id=plugin_id,
|
|
module=module,
|
|
class_name=class_name
|
|
)
|
|
|
|
# Merge base config with plugin-specific defaults
|
|
config = self.base_config.copy()
|
|
|
|
plugin_instance = self.plugin_loader.instantiate_plugin(
|
|
plugin_id=plugin_id,
|
|
plugin_class=plugin_class,
|
|
config=config,
|
|
display_manager=self.mock_display_manager,
|
|
cache_manager=self.mock_cache_manager,
|
|
plugin_manager=self.mock_plugin_manager
|
|
)
|
|
|
|
assert plugin_instance is not None
|
|
assert plugin_instance.plugin_id == plugin_id
|
|
assert plugin_instance.enabled == config.get('enabled', True)
|
|
|
|
def test_plugin_has_required_methods(self, plugin_id: str):
|
|
"""Test that plugin has required BasePlugin methods."""
|
|
manifest = self.load_plugin_manifest(plugin_id)
|
|
plugin_dir = self.plugins_dir / plugin_id
|
|
entry_point = manifest.get('entry_point', 'manager.py')
|
|
class_name = manifest['class_name']
|
|
|
|
module = self.plugin_loader.load_module(
|
|
plugin_id=plugin_id,
|
|
plugin_dir=plugin_dir,
|
|
entry_point=entry_point
|
|
)
|
|
|
|
plugin_class = self.plugin_loader.get_plugin_class(
|
|
plugin_id=plugin_id,
|
|
module=module,
|
|
class_name=class_name
|
|
)
|
|
|
|
config = self.base_config.copy()
|
|
plugin_instance = self.plugin_loader.instantiate_plugin(
|
|
plugin_id=plugin_id,
|
|
plugin_class=plugin_class,
|
|
config=config,
|
|
display_manager=self.mock_display_manager,
|
|
cache_manager=self.mock_cache_manager,
|
|
plugin_manager=self.mock_plugin_manager
|
|
)
|
|
|
|
# Check required methods exist
|
|
assert hasattr(plugin_instance, 'update')
|
|
assert hasattr(plugin_instance, 'display')
|
|
assert callable(plugin_instance.update)
|
|
assert callable(plugin_instance.display)
|
|
|
|
def test_plugin_update_method(self, plugin_id: str):
|
|
"""Test that plugin update() method can be called without errors."""
|
|
manifest = self.load_plugin_manifest(plugin_id)
|
|
plugin_dir = self.plugins_dir / plugin_id
|
|
entry_point = manifest.get('entry_point', 'manager.py')
|
|
class_name = manifest['class_name']
|
|
|
|
module = self.plugin_loader.load_module(
|
|
plugin_id=plugin_id,
|
|
plugin_dir=plugin_dir,
|
|
entry_point=entry_point
|
|
)
|
|
|
|
plugin_class = self.plugin_loader.get_plugin_class(
|
|
plugin_id=plugin_id,
|
|
module=module,
|
|
class_name=class_name
|
|
)
|
|
|
|
config = self.base_config.copy()
|
|
plugin_instance = self.plugin_loader.instantiate_plugin(
|
|
plugin_id=plugin_id,
|
|
plugin_class=plugin_class,
|
|
config=config,
|
|
display_manager=self.mock_display_manager,
|
|
cache_manager=self.mock_cache_manager,
|
|
plugin_manager=self.mock_plugin_manager
|
|
)
|
|
|
|
# Call update() - should not raise exceptions
|
|
# Some plugins may need API keys, but they should handle that gracefully
|
|
try:
|
|
plugin_instance.update()
|
|
except Exception as e:
|
|
# If it's a missing API key or similar, that's acceptable for integration tests
|
|
error_msg = str(e).lower()
|
|
if 'api' in error_msg or 'key' in error_msg or 'auth' in error_msg or 'credential' in error_msg:
|
|
pytest.skip(f"Plugin requires API credentials: {e}")
|
|
else:
|
|
raise
|
|
|
|
def test_plugin_display_method(self, plugin_id: str):
|
|
"""Test that plugin display() method can be called without errors."""
|
|
manifest = self.load_plugin_manifest(plugin_id)
|
|
plugin_dir = self.plugins_dir / plugin_id
|
|
entry_point = manifest.get('entry_point', 'manager.py')
|
|
class_name = manifest['class_name']
|
|
|
|
module = self.plugin_loader.load_module(
|
|
plugin_id=plugin_id,
|
|
plugin_dir=plugin_dir,
|
|
entry_point=entry_point
|
|
)
|
|
|
|
plugin_class = self.plugin_loader.get_plugin_class(
|
|
plugin_id=plugin_id,
|
|
module=module,
|
|
class_name=class_name
|
|
)
|
|
|
|
config = self.base_config.copy()
|
|
plugin_instance = self.plugin_loader.instantiate_plugin(
|
|
plugin_id=plugin_id,
|
|
plugin_class=plugin_class,
|
|
config=config,
|
|
display_manager=self.mock_display_manager,
|
|
cache_manager=self.mock_cache_manager,
|
|
plugin_manager=self.mock_plugin_manager
|
|
)
|
|
|
|
# Some plugins need matrix attribute on display_manager (set before update)
|
|
if not hasattr(self.mock_display_manager, 'matrix'):
|
|
from unittest.mock import MagicMock
|
|
self.mock_display_manager.matrix = MagicMock()
|
|
self.mock_display_manager.matrix.width = 128
|
|
self.mock_display_manager.matrix.height = 32
|
|
|
|
# Call update() first if needed
|
|
try:
|
|
plugin_instance.update()
|
|
except Exception as e:
|
|
error_msg = str(e).lower()
|
|
if 'api' in error_msg or 'key' in error_msg or 'auth' in error_msg:
|
|
pytest.skip(f"Plugin requires API credentials: {e}")
|
|
|
|
# Some plugins need a mode set before display
|
|
# Try to set a mode if the plugin has that capability
|
|
if hasattr(plugin_instance, 'set_mode') and manifest.get('display_modes'):
|
|
try:
|
|
first_mode = manifest['display_modes'][0]
|
|
plugin_instance.set_mode(first_mode)
|
|
except Exception:
|
|
pass # If set_mode doesn't exist or fails, continue
|
|
|
|
# Call display() - should not raise exceptions
|
|
try:
|
|
plugin_instance.display(force_clear=True)
|
|
except Exception as e:
|
|
# Some plugins may need specific setup - if it's a mode issue, that's acceptable
|
|
error_msg = str(e).lower()
|
|
if 'mode' in error_msg or 'manager' in error_msg:
|
|
# This is acceptable - plugin needs proper mode setup
|
|
pass
|
|
else:
|
|
raise
|
|
|
|
# Verify display_manager methods were called (if display succeeded)
|
|
# Some plugins may not call these if they skip display due to missing data
|
|
# So we just verify the method was callable without exceptions
|
|
assert hasattr(plugin_instance, 'display')
|
|
|
|
def test_plugin_has_display_modes(self, plugin_id: str):
|
|
"""Test that plugin has display modes defined."""
|
|
manifest = self.load_plugin_manifest(plugin_id)
|
|
|
|
assert 'display_modes' in manifest
|
|
assert isinstance(manifest['display_modes'], list)
|
|
assert len(manifest['display_modes']) > 0
|
|
|
|
def test_config_schema_valid(self, plugin_id: str):
|
|
"""Test that config schema is valid JSON if it exists."""
|
|
schema = self.load_plugin_config_schema(plugin_id)
|
|
|
|
if schema is not None:
|
|
assert isinstance(schema, dict)
|
|
# Schema should have 'type' field for JSON Schema
|
|
assert 'type' in schema or 'properties' in schema
|