diff --git a/src/batcontrol/core.py b/src/batcontrol/core.py index a21b0592..5d6c4768 100644 --- a/src/batcontrol/core.py +++ b/src/batcontrol/core.py @@ -497,7 +497,9 @@ def run(self): 1 - elapsed_in_current ) - this_logic_run = LogicFactory.create_logic(self.config, self.timezone) + this_logic_run = LogicFactory.create_logic(self.time_resolution, + self.config, + self.timezone) # Create input for calculation calc_input = CalculationInput( diff --git a/src/batcontrol/logic/logic.py b/src/batcontrol/logic/logic.py index 1e2fb068..8a0453d0 100644 --- a/src/batcontrol/logic/logic.py +++ b/src/batcontrol/logic/logic.py @@ -10,16 +10,15 @@ class Logic: """ Factory for logic classes. """ print_class_message = True @staticmethod - def create_logic(config: dict, timezone) -> LogicInterface: + def create_logic(time_resolution_minutes: int, config: dict, timezone) -> LogicInterface: """ Select and configure a logic class based on the given configuration """ request_type = config.get('type', 'default').lower() - interval_minutes = config.get('time_resolution_minutes', 60) logic = None if request_type == 'default': if Logic.print_class_message: logger.info('Using default logic') Logic.print_class_message = False - logic = DefaultLogic(timezone, interval_minutes=interval_minutes) + logic = DefaultLogic(timezone, interval_minutes=time_resolution_minutes) if config.get('battery_control_expert', None) is not None: battery_control_expert = config.get( 'battery_control_expert', {}) attribute_list = [ diff --git a/tests/batcontrol/test_core.py b/tests/batcontrol/test_core.py index 4482b8b4..633566b6 100644 --- a/tests/batcontrol/test_core.py +++ b/tests/batcontrol/test_core.py @@ -1,4 +1,5 @@ """Tests for core batcontrol functionality including MODE_LIMIT_BATTERY_CHARGE_RATE""" +import datetime import pytest from unittest.mock import MagicMock, patch @@ -6,6 +7,7 @@ Batcontrol, MODE_LIMIT_BATTERY_CHARGE_RATE, ) +from batcontrol.logic.logic import Logic as LogicFactory class TestModeLimitBatteryChargeRate: @@ -287,5 +289,72 @@ def test_api_set_limit_applies_immediately_in_mode_8( mock_inverter.set_mode_limit_battery_charge.assert_called_once_with(2000) +class TestTimeResolutionString: + """Test that time_resolution_minutes provided as string (e.g. from Home Assistant) is handled correctly""" + + @pytest.fixture + def base_mock_config(self): + """Provide a minimal config for testing""" + return { + 'timezone': 'Europe/Berlin', + 'inverter': { + 'type': 'dummy', + 'max_grid_charge_rate': 5000, + 'max_pv_charge_rate': 3000, + 'min_pv_charge_rate': 100 + }, + 'utility': { + 'type': 'tibber', + 'token': 'test_token' + }, + 'pvinstallations': [], + 'consumption_forecast': { + 'type': 'simple', + 'value': 500 + }, + 'battery_control': { + 'max_charging_from_grid_limit': 0.8, + 'min_price_difference': 0.05 + }, + 'mqtt': { + 'enabled': False + } + } + + @pytest.mark.parametrize('resolution_str,expected_int', [('60', 60), ('15', 15)]) + @patch('batcontrol.core.tariff_factory.create_tarif_provider') + @patch('batcontrol.core.inverter_factory.create_inverter') + @patch('batcontrol.core.solar_factory.create_solar_provider') + @patch('batcontrol.core.consumption_factory.create_consumption') + def test_string_time_resolution_initialises_without_error( + self, mock_consumption, mock_solar, mock_inverter_factory, mock_tariff, + base_mock_config, resolution_str, expected_int + ): + """Batcontrol must not crash when time_resolution_minutes is a string""" + mock_inverter = MagicMock() + mock_inverter.get_max_capacity = MagicMock(return_value=10000) + mock_inverter_factory.return_value = mock_inverter + mock_tariff.return_value = MagicMock() + mock_solar.return_value = MagicMock() + mock_consumption.return_value = MagicMock() + + base_mock_config['time_resolution_minutes'] = resolution_str + bc = Batcontrol(base_mock_config) + + assert isinstance(bc.time_resolution, int) + assert bc.time_resolution == expected_int + + @pytest.mark.parametrize('resolution_str', ['60', '15']) + def test_logic_factory_accepts_string_resolution_as_int(self, resolution_str): + """Logic factory must produce a valid logic instance when given an int resolution""" + logic = LogicFactory.create_logic( + int(resolution_str), + {'type': 'default'}, + datetime.timezone.utc + ) + assert logic is not None + assert logic.interval_minutes == int(resolution_str) + + if __name__ == '__main__': pytest.main([__file__, '-v'])