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:
5ymb01
2026-03-20 15:06:58 -04:00
committed by GitHub
parent fa92bfbdd8
commit 28a374485f
4 changed files with 47 additions and 41 deletions

1
test/__init__.py Normal file
View File

@@ -0,0 +1 @@
# Test package

View File

@@ -23,6 +23,7 @@ if str(project_root) not in sys.path:
sys.path.insert(0, str(project_root))
from src.config_manager import ConfigManager
from src.exceptions import ConfigError
from src.plugin_system.schema_manager import SchemaManager
@@ -30,35 +31,31 @@ class TestInvalidJson:
"""Test handling of invalid JSON in config files."""
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.write_text("{ invalid json }")
with patch.object(ConfigManager, '_get_config_path', return_value=str(config_file)):
config_manager = ConfigManager(config_dir=str(tmp_path))
# Should not raise, should return empty or default config
config = config_manager.load_config()
assert isinstance(config, dict)
config_manager = ConfigManager(config_path=str(config_file))
with pytest.raises(ConfigError):
config_manager.load_config()
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.write_text('{"plugin": {"enabled": true') # Missing closing braces
with patch.object(ConfigManager, '_get_config_path', return_value=str(config_file)):
config_manager = ConfigManager(config_dir=str(tmp_path))
config = config_manager.load_config()
assert isinstance(config, dict)
config_manager = ConfigManager(config_path=str(config_file))
with pytest.raises(ConfigError):
config_manager.load_config()
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.write_text("")
with patch.object(ConfigManager, '_get_config_path', return_value=str(config_file)):
config_manager = ConfigManager(config_dir=str(tmp_path))
config = config_manager.load_config()
assert isinstance(config, dict)
config_manager = ConfigManager(config_path=str(config_file))
with pytest.raises(ConfigError):
config_manager.load_config()
class TestTypeValidation:

View File

@@ -209,49 +209,47 @@ class TestDisplayControllerSchedule:
def test_schedule_disabled(self, test_display_controller):
"""Test when schedule is disabled."""
controller = test_display_controller
controller.config = {"schedule": {"enabled": False}}
controller._check_schedule()
assert controller.is_display_active is True
schedule_config = {"schedule": {"enabled": False}}
with patch.object(controller.config_service, 'get_config', return_value=schedule_config):
controller._check_schedule()
assert controller.is_display_active is True
def test_active_hours(self, test_display_controller):
"""Test active hours check."""
controller = test_display_controller
# Mock datetime to be within active hours
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.time.return_value = datetime.strptime("12:00", "%H:%M").time()
mock_datetime.strptime = datetime.strptime
controller.config = {
schedule_config = {
"schedule": {
"enabled": True,
"start_time": "09:00",
"end_time": "17:00"
}
}
controller._check_schedule()
assert controller.is_display_active is True
with patch.object(controller.config_service, 'get_config', return_value=schedule_config):
controller._check_schedule()
assert controller.is_display_active is True
def test_inactive_hours(self, test_display_controller):
"""Test inactive hours check."""
controller = test_display_controller
# Mock datetime to be outside active hours
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.time.return_value = datetime.strptime("20:00", "%H:%M").time()
mock_datetime.strptime = datetime.strptime
controller.config = {
schedule_config = {
"schedule": {
"enabled": True,
"start_time": "09:00",
"end_time": "17:00"
}
}
controller._check_schedule()
assert controller.is_display_active is False
with patch.object(controller.config_service, 'get_config', return_value=schedule_config):
controller._check_schedule()
assert controller.is_display_active is False
from datetime import datetime

View File

@@ -29,7 +29,13 @@ def mock_config_manager():
}
mock.get_config_path.return_value = 'config/config.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(
status=MagicMock(value='success'),
message=None
@@ -385,15 +391,19 @@ class TestPluginsAPI:
def test_get_plugin_config(self, client, mock_config_manager):
"""Test getting plugin configuration."""
# Plugin configs live at top-level keys (not under 'plugins')
mock_config_manager.load_config.return_value = {
'plugins': {
'weather': {
'enabled': True,
'api_key': 'test_key'
}
'weather': {
'enabled': True,
'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')
assert response.status_code == 200