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)) 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:

View File

@@ -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

View File

@@ -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')