Files
LEDMatrix/test/plugins/test_plugin_base.py
Chuck 55161f309b fix: remove unused imports and bare exception aliases (pyflakes F401/F841)
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>
2026-05-14 10:41:55 -04:00

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