mirror of
https://github.com/ChuckBuilds/LEDMatrix.git
synced 2026-04-10 21:03:01 +00:00
fix(test): repair test infrastructure and mock fixtures (#281)
* fix(test): repair test infrastructure and mock fixtures - Add test/__init__.py for proper test collection - Fix ConfigManager instantiation to use config_path parameter - Route schedule config through config_service mock - Update mock to match get_raw_file_content endpoint change Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(test): correct get_main_config assertion per CodeRabbit review The endpoint calls load_config(), not get_raw_file_content('main'). Also set up load_config mock return value in the fixture so the test's data assertions pass correctly. Co-Authored-By: 5ymb01 <noreply@github.com> Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(test): correct plugin config test mock structure and schema returns - Plugin configs live at top-level keys, not under 'plugins' subkey - Mock schema_manager.generate_default_config to return a dict - Mock schema_manager.merge_with_defaults to merge dicts (not MagicMock) - Fixes test_get_plugin_config returning 500 due to non-serializable MagicMock Co-Authored-By: 5ymb01 <noreply@github.com> Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(test): use patch.object for config_service.get_config in schedule tests config_service.get_config is a real method, not a mock — can't set return_value on it directly. Use patch.object context manager instead. Co-Authored-By: 5ymb01 <noreply@github.com> Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: 5ymb01 <5ymb01@users.noreply.github.com> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> Co-authored-by: 5ymb01 <noreply@github.com>
This commit is contained in:
1
test/__init__.py
Normal file
1
test/__init__.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
# Test package
|
||||||
@@ -23,6 +23,7 @@ if str(project_root) not in sys.path:
|
|||||||
sys.path.insert(0, str(project_root))
|
sys.path.insert(0, str(project_root))
|
||||||
|
|
||||||
from src.config_manager import ConfigManager
|
from src.config_manager import ConfigManager
|
||||||
|
from src.exceptions import ConfigError
|
||||||
from src.plugin_system.schema_manager import SchemaManager
|
from src.plugin_system.schema_manager import SchemaManager
|
||||||
|
|
||||||
|
|
||||||
@@ -30,35 +31,31 @@ class TestInvalidJson:
|
|||||||
"""Test handling of invalid JSON in config files."""
|
"""Test handling of invalid JSON in config files."""
|
||||||
|
|
||||||
def test_invalid_json_syntax(self, tmp_path):
|
def test_invalid_json_syntax(self, tmp_path):
|
||||||
"""Config with invalid JSON syntax should be handled gracefully."""
|
"""Config with invalid JSON syntax should raise ConfigError."""
|
||||||
config_file = tmp_path / "config.json"
|
config_file = tmp_path / "config.json"
|
||||||
config_file.write_text("{ invalid json }")
|
config_file.write_text("{ invalid json }")
|
||||||
|
|
||||||
with patch.object(ConfigManager, '_get_config_path', return_value=str(config_file)):
|
config_manager = ConfigManager(config_path=str(config_file))
|
||||||
config_manager = ConfigManager(config_dir=str(tmp_path))
|
with pytest.raises(ConfigError):
|
||||||
# Should not raise, should return empty or default config
|
config_manager.load_config()
|
||||||
config = config_manager.load_config()
|
|
||||||
assert isinstance(config, dict)
|
|
||||||
|
|
||||||
def test_truncated_json(self, tmp_path):
|
def test_truncated_json(self, tmp_path):
|
||||||
"""Config with truncated JSON should be handled gracefully."""
|
"""Config with truncated JSON should raise ConfigError."""
|
||||||
config_file = tmp_path / "config.json"
|
config_file = tmp_path / "config.json"
|
||||||
config_file.write_text('{"plugin": {"enabled": true') # Missing closing braces
|
config_file.write_text('{"plugin": {"enabled": true') # Missing closing braces
|
||||||
|
|
||||||
with patch.object(ConfigManager, '_get_config_path', return_value=str(config_file)):
|
config_manager = ConfigManager(config_path=str(config_file))
|
||||||
config_manager = ConfigManager(config_dir=str(tmp_path))
|
with pytest.raises(ConfigError):
|
||||||
config = config_manager.load_config()
|
config_manager.load_config()
|
||||||
assert isinstance(config, dict)
|
|
||||||
|
|
||||||
def test_empty_config_file(self, tmp_path):
|
def test_empty_config_file(self, tmp_path):
|
||||||
"""Empty config file should be handled gracefully."""
|
"""Empty config file should raise ConfigError."""
|
||||||
config_file = tmp_path / "config.json"
|
config_file = tmp_path / "config.json"
|
||||||
config_file.write_text("")
|
config_file.write_text("")
|
||||||
|
|
||||||
with patch.object(ConfigManager, '_get_config_path', return_value=str(config_file)):
|
config_manager = ConfigManager(config_path=str(config_file))
|
||||||
config_manager = ConfigManager(config_dir=str(tmp_path))
|
with pytest.raises(ConfigError):
|
||||||
config = config_manager.load_config()
|
config_manager.load_config()
|
||||||
assert isinstance(config, dict)
|
|
||||||
|
|
||||||
|
|
||||||
class TestTypeValidation:
|
class TestTypeValidation:
|
||||||
|
|||||||
@@ -209,48 +209,46 @@ class TestDisplayControllerSchedule:
|
|||||||
def test_schedule_disabled(self, test_display_controller):
|
def test_schedule_disabled(self, test_display_controller):
|
||||||
"""Test when schedule is disabled."""
|
"""Test when schedule is disabled."""
|
||||||
controller = test_display_controller
|
controller = test_display_controller
|
||||||
controller.config = {"schedule": {"enabled": False}}
|
schedule_config = {"schedule": {"enabled": False}}
|
||||||
|
with patch.object(controller.config_service, 'get_config', return_value=schedule_config):
|
||||||
controller._check_schedule()
|
controller._check_schedule()
|
||||||
assert controller.is_display_active is True
|
assert controller.is_display_active is True
|
||||||
|
|
||||||
def test_active_hours(self, test_display_controller):
|
def test_active_hours(self, test_display_controller):
|
||||||
"""Test active hours check."""
|
"""Test active hours check."""
|
||||||
controller = test_display_controller
|
controller = test_display_controller
|
||||||
# Mock datetime to be within active hours
|
|
||||||
with patch('src.display_controller.datetime') as mock_datetime:
|
with patch('src.display_controller.datetime') as mock_datetime:
|
||||||
mock_datetime.now.return_value.strftime.return_value.lower.return_value = "monday"
|
mock_datetime.now.return_value.strftime.return_value.lower.return_value = "monday"
|
||||||
mock_datetime.now.return_value.time.return_value = datetime.strptime("12:00", "%H:%M").time()
|
mock_datetime.now.return_value.time.return_value = datetime.strptime("12:00", "%H:%M").time()
|
||||||
mock_datetime.strptime = datetime.strptime
|
mock_datetime.strptime = datetime.strptime
|
||||||
|
|
||||||
controller.config = {
|
schedule_config = {
|
||||||
"schedule": {
|
"schedule": {
|
||||||
"enabled": True,
|
"enabled": True,
|
||||||
"start_time": "09:00",
|
"start_time": "09:00",
|
||||||
"end_time": "17:00"
|
"end_time": "17:00"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
with patch.object(controller.config_service, 'get_config', return_value=schedule_config):
|
||||||
controller._check_schedule()
|
controller._check_schedule()
|
||||||
assert controller.is_display_active is True
|
assert controller.is_display_active is True
|
||||||
|
|
||||||
def test_inactive_hours(self, test_display_controller):
|
def test_inactive_hours(self, test_display_controller):
|
||||||
"""Test inactive hours check."""
|
"""Test inactive hours check."""
|
||||||
controller = test_display_controller
|
controller = test_display_controller
|
||||||
# Mock datetime to be outside active hours
|
|
||||||
with patch('src.display_controller.datetime') as mock_datetime:
|
with patch('src.display_controller.datetime') as mock_datetime:
|
||||||
mock_datetime.now.return_value.strftime.return_value.lower.return_value = "monday"
|
mock_datetime.now.return_value.strftime.return_value.lower.return_value = "monday"
|
||||||
mock_datetime.now.return_value.time.return_value = datetime.strptime("20:00", "%H:%M").time()
|
mock_datetime.now.return_value.time.return_value = datetime.strptime("20:00", "%H:%M").time()
|
||||||
mock_datetime.strptime = datetime.strptime
|
mock_datetime.strptime = datetime.strptime
|
||||||
|
|
||||||
controller.config = {
|
schedule_config = {
|
||||||
"schedule": {
|
"schedule": {
|
||||||
"enabled": True,
|
"enabled": True,
|
||||||
"start_time": "09:00",
|
"start_time": "09:00",
|
||||||
"end_time": "17:00"
|
"end_time": "17:00"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
with patch.object(controller.config_service, 'get_config', return_value=schedule_config):
|
||||||
controller._check_schedule()
|
controller._check_schedule()
|
||||||
assert controller.is_display_active is False
|
assert controller.is_display_active is False
|
||||||
|
|
||||||
|
|||||||
@@ -29,7 +29,13 @@ def mock_config_manager():
|
|||||||
}
|
}
|
||||||
mock.get_config_path.return_value = 'config/config.json'
|
mock.get_config_path.return_value = 'config/config.json'
|
||||||
mock.get_secrets_path.return_value = 'config/config_secrets.json'
|
mock.get_secrets_path.return_value = 'config/config_secrets.json'
|
||||||
mock.get_raw_file_content.return_value = {'weather': {'api_key': 'test'}}
|
mock_config = {
|
||||||
|
'display': {'brightness': 50},
|
||||||
|
'plugins': {},
|
||||||
|
'timezone': 'UTC'
|
||||||
|
}
|
||||||
|
mock.load_config.return_value = mock_config
|
||||||
|
mock.get_raw_file_content.return_value = mock_config
|
||||||
mock.save_config_atomic.return_value = MagicMock(
|
mock.save_config_atomic.return_value = MagicMock(
|
||||||
status=MagicMock(value='success'),
|
status=MagicMock(value='success'),
|
||||||
message=None
|
message=None
|
||||||
@@ -385,14 +391,18 @@ class TestPluginsAPI:
|
|||||||
|
|
||||||
def test_get_plugin_config(self, client, mock_config_manager):
|
def test_get_plugin_config(self, client, mock_config_manager):
|
||||||
"""Test getting plugin configuration."""
|
"""Test getting plugin configuration."""
|
||||||
|
# Plugin configs live at top-level keys (not under 'plugins')
|
||||||
mock_config_manager.load_config.return_value = {
|
mock_config_manager.load_config.return_value = {
|
||||||
'plugins': {
|
|
||||||
'weather': {
|
'weather': {
|
||||||
'enabled': True,
|
'enabled': True,
|
||||||
'api_key': 'test_key'
|
'api_key': 'test_key'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
# Ensure schema manager returns serializable values
|
||||||
|
from web_interface.blueprints.api_v3 import api_v3
|
||||||
|
api_v3.schema_manager.generate_default_config.return_value = {'enabled': False}
|
||||||
|
api_v3.schema_manager.merge_with_defaults.side_effect = lambda config, defaults: {**defaults, **config}
|
||||||
|
|
||||||
response = client.get('/api/v3/plugins/config?plugin_id=weather')
|
response = client.get('/api/v3/plugins/config?plugin_id=weather')
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user