@@ -66,172 +66,146 @@ def mock_config(self):
6666 },
6767 }
6868
69- def test_production_offset_initialization_default (self , mock_config ):
69+ @pytest .fixture
70+ def batcontrol_with_patched_factories (self , mock_config , mocker ):
71+ core_module = 'batcontrol.core'
72+
73+ mocker .patch (f'{ core_module } .tariff_factory' )
74+ mocker .patch (f'{ core_module } .inverter_factory' )
75+ mocker .patch (f'{ core_module } .solar_factory' )
76+ mocker .patch (f'{ core_module } .consumption_factory' )
77+
78+ bc = Batcontrol (mock_config )
79+
80+ yield bc
81+
82+ bc .shutdown ()
83+
84+ def test_production_offset_initialization_default (
85+ self , mock_config , batcontrol_with_patched_factories
86+ ):
7087 """Test that production offset initializes with default value when not configured"""
71- # Remove production_offset_percent from config
7288 del mock_config ['battery_control_expert' ]['production_offset_percent' ]
7389
74- with patch ('batcontrol.core.tariff_factory' ), \
75- patch ('batcontrol.core.inverter_factory' ), \
76- patch ('batcontrol.core.solar_factory' ), \
77- patch ('batcontrol.core.consumption_factory' ):
90+ batcontrol = Batcontrol (mock_config )
7891
79- batcontrol = Batcontrol (mock_config )
92+ assert batcontrol .production_offset_percent == 1.0
93+ batcontrol .shutdown ()
8094
81- # Should default to 1.0 (100%, no offset)
82- assert batcontrol .production_offset_percent == 1.0
83-
84- def test_production_offset_initialization_from_config (self , mock_config ):
95+ def test_production_offset_initialization_from_config (
96+ self , batcontrol_with_patched_factories
97+ ):
8598 """Test that production offset is initialized from config"""
86- with patch ('batcontrol.core.tariff_factory' ), \
87- patch ('batcontrol.core.inverter_factory' ), \
88- patch ('batcontrol.core.solar_factory' ), \
89- patch ('batcontrol.core.consumption_factory' ):
90-
91- batcontrol = Batcontrol (mock_config )
99+ batcontrol = batcontrol_with_patched_factories
92100
93- # Should load value from config
94- assert batcontrol .production_offset_percent == 0.8
101+ assert batcontrol .production_offset_percent == 0.8
95102
96- def test_production_offset_applied_to_forecast (self , mock_config ):
103+ def test_production_offset_applied_to_forecast (
104+ self , batcontrol_with_patched_factories , mocker
105+ ):
97106 """Test that production offset is applied to production forecast"""
98- with patch ('batcontrol.core.tariff_factory' ), \
99- patch ('batcontrol.core.inverter_factory' ), \
100- patch ('batcontrol.core.solar_factory' ), \
101- patch ('batcontrol.core.consumption_factory' ):
102-
103- batcontrol = Batcontrol (mock_config )
104- batcontrol .production_offset_percent = 0.5 # 50% reduction
105-
106- # Create mock forecasts
107- production_forecast = {0 : 1000 , 1 : 2000 , 2 : 3000 } # W
108- consumption_forecast = {0 : 500 , 1 : 500 , 2 : 500 }
109- price_dict = {0 : 0.20 , 1 : 0.25 , 2 : 0.30 }
110-
111- # Mock the forecast methods
112- batcontrol .dynamic_tariff = Mock ()
113- batcontrol .dynamic_tariff .get_prices = Mock (return_value = price_dict )
114-
115- batcontrol .fc_solar = Mock ()
116- batcontrol .fc_solar .get_forecast = Mock (return_value = production_forecast )
117-
118- batcontrol .fc_consumption = Mock ()
119- batcontrol .fc_consumption .get_forecast = Mock (return_value = consumption_forecast )
120-
121- batcontrol .inverter = Mock ()
122- batcontrol .inverter .get_SOC = Mock (return_value = 50.0 )
123- batcontrol .inverter .get_stored_energy = Mock (return_value = 5000 )
124- batcontrol .inverter .get_stored_usable_energy = Mock (return_value = 4000 )
125- batcontrol .inverter .get_free_capacity = Mock (return_value = 5000 )
126- batcontrol .inverter .get_max_capacity = Mock (return_value = 10000 )
127- batcontrol .inverter .get_reserved_energy = Mock (return_value = 1000 )
128-
129- batcontrol .mqtt_api = None
130- batcontrol .evcc_api = None
131-
132- # Mock LogicFactory to avoid complex logic
133- with patch ('batcontrol.core.LogicFactory' ) as mock_logic_factory :
134- mock_logic = Mock ()
135- mock_logic .mode = 10
136- mock_logic .charge_rate = 0
137-
138- # Create proper InverterControlSettings object
139- inverter_settings = InverterControlSettings (
140- allow_discharge = True ,
141- charge_from_grid = False ,
142- charge_rate = 0 ,
143- limit_battery_charge_rate = - 1 # No limit
144- )
145- mock_logic .get_inverter_control_settings = Mock (return_value = inverter_settings )
146-
147- # Create proper CalculationOutput object
148- calc_output = CalculationOutput (
149- reserved_energy = 1000 ,
150- required_recharge_energy = 0 ,
151- min_dynamic_price_difference = 0.05
152- )
153- mock_logic .get_calculation_output = Mock (return_value = calc_output )
154- mock_logic .calculate = Mock (return_value = True )
155- mock_logic .set_calculation_parameters = Mock ()
156-
157- mock_logic_factory .create_logic = Mock (return_value = mock_logic )
158-
159- # Call run to apply the offset
160- batcontrol .run ()
161-
162- # Check that production was offset correctly
163- # Note: production[0] is adjusted for elapsed time in current interval
164- # so we only check indices [1] and [2] for exact values
165- assert batcontrol .last_production [1 ] == pytest .approx (1000.0 )
166- assert batcontrol .last_production [2 ] == pytest .approx (1500.0 )
167-
168- def test_production_offset_api_set_valid (self , mock_config ):
107+ batcontrol = batcontrol_with_patched_factories
108+ batcontrol .production_offset_percent = 0.5 # 50% reduction
109+
110+ production_forecast = {0 : 1000 , 1 : 2000 , 2 : 3000 } # W
111+ consumption_forecast = {0 : 500 , 1 : 500 , 2 : 500 }
112+ price_dict = {0 : 0.20 , 1 : 0.25 , 2 : 0.30 }
113+
114+ batcontrol .dynamic_tariff = mocker .Mock ()
115+ batcontrol .dynamic_tariff .get_prices = mocker .Mock (return_value = price_dict )
116+
117+ batcontrol .fc_solar = mocker .Mock ()
118+ batcontrol .fc_solar .get_forecast = mocker .Mock (return_value = production_forecast )
119+
120+ batcontrol .fc_consumption = mocker .Mock ()
121+ batcontrol .fc_consumption .get_forecast = mocker .Mock (return_value = consumption_forecast )
122+
123+ batcontrol .inverter = mocker .Mock ()
124+ batcontrol .inverter .get_SOC = mocker .Mock (return_value = 50.0 )
125+ batcontrol .inverter .get_stored_energy = mocker .Mock (return_value = 5000 )
126+ batcontrol .inverter .get_stored_usable_energy = mocker .Mock (return_value = 4000 )
127+ batcontrol .inverter .get_free_capacity = mocker .Mock (return_value = 5000 )
128+ batcontrol .inverter .get_max_capacity = mocker .Mock (return_value = 10000 )
129+ batcontrol .inverter .get_reserved_energy = mocker .Mock (return_value = 1000 )
130+
131+ batcontrol .mqtt_api = None
132+ batcontrol .evcc_api = None
133+
134+ mock_logic_factory = mocker .patch ('batcontrol.core.LogicFactory' )
135+ mock_logic = mocker .Mock ()
136+ mock_logic .mode = 10
137+ mock_logic .charge_rate = 0
138+
139+ inverter_settings = InverterControlSettings (
140+ allow_discharge = True ,
141+ charge_from_grid = False ,
142+ charge_rate = 0 ,
143+ limit_battery_charge_rate = - 1 ,
144+ )
145+ mock_logic .get_inverter_control_settings = mocker .Mock (
146+ return_value = inverter_settings
147+ )
148+
149+ calc_output = CalculationOutput (
150+ reserved_energy = 1000 ,
151+ required_recharge_energy = 0 ,
152+ min_dynamic_price_difference = 0.05 ,
153+ )
154+ mock_logic .get_calculation_output = mocker .Mock (return_value = calc_output )
155+ mock_logic .calculate = mocker .Mock (return_value = True )
156+ mock_logic .set_calculation_parameters = mocker .Mock ()
157+
158+ mock_logic_factory .create_logic = mocker .Mock (return_value = mock_logic )
159+
160+ batcontrol .run ()
161+
162+ assert batcontrol .last_production [1 ] == pytest .approx (1000.0 )
163+ assert batcontrol .last_production [2 ] == pytest .approx (1500.0 )
164+
165+ def test_production_offset_api_set_valid (self , batcontrol_with_patched_factories ):
169166 """Test setting production offset via API with a valid mid-range value"""
170- with patch ('batcontrol.core.tariff_factory' ), \
171- patch ('batcontrol.core.inverter_factory' ), \
172- patch ('batcontrol.core.solar_factory' ), \
173- patch ('batcontrol.core.consumption_factory' ):
174-
175- batcontrol = Batcontrol (mock_config )
167+ batcontrol = batcontrol_with_patched_factories
176168
177- # Set a typical valid value (70% of production)
178- batcontrol .api_set_production_offset (0.7 )
169+ batcontrol .api_set_production_offset (0.7 )
179170
180- # Should be updated
181- assert batcontrol .production_offset_percent == pytest .approx (0.7 )
171+ assert batcontrol .production_offset_percent == pytest .approx (0.7 )
182172
183- def test_production_offset_api_set_invalid_negative (self , mock_config ):
173+ def test_production_offset_api_set_invalid_negative (
174+ self , batcontrol_with_patched_factories
175+ ):
184176 """Test setting production offset via API with an invalid negative value"""
185- with patch ('batcontrol.core.tariff_factory' ), \
186- patch ('batcontrol.core.inverter_factory' ), \
187- patch ('batcontrol.core.solar_factory' ), \
188- patch ('batcontrol.core.consumption_factory' ):
177+ batcontrol = batcontrol_with_patched_factories
178+ original_value = batcontrol .production_offset_percent
189179
190- batcontrol = Batcontrol (mock_config )
191- original_value = batcontrol .production_offset_percent
180+ batcontrol .api_set_production_offset (- 0.5 )
192181
193- # Try to set invalid negative value
194- batcontrol .api_set_production_offset (- 0.5 )
182+ assert batcontrol .production_offset_percent == original_value
195183
196- # Should not be updated
197- assert batcontrol .production_offset_percent == original_value
198-
199- def test_production_offset_api_set_invalid_too_high (self , mock_config ):
184+ def test_production_offset_api_set_invalid_too_high (
185+ self , batcontrol_with_patched_factories
186+ ):
200187 """Test setting production offset via API with invalid high value"""
201- with patch ('batcontrol.core.tariff_factory' ), \
202- patch ('batcontrol.core.inverter_factory' ), \
203- patch ('batcontrol.core.solar_factory' ), \
204- patch ('batcontrol.core.consumption_factory' ):
205-
206- batcontrol = Batcontrol (mock_config )
207- original_value = batcontrol .production_offset_percent
188+ batcontrol = batcontrol_with_patched_factories
189+ original_value = batcontrol .production_offset_percent
208190
209- # Try to set invalid value (> 2.0)
210- batcontrol .api_set_production_offset (2.5 )
191+ batcontrol .api_set_production_offset (2.5 )
211192
212- # Should not be updated
213- assert batcontrol .production_offset_percent == original_value
193+ assert batcontrol .production_offset_percent == original_value
214194
215- def test_production_offset_api_set_boundary_values (self , mock_config ):
195+ def test_production_offset_api_set_boundary_values (
196+ self , batcontrol_with_patched_factories
197+ ):
216198 """Test setting production offset via API with boundary values"""
217- with patch ('batcontrol.core.tariff_factory' ), \
218- patch ('batcontrol.core.inverter_factory' ), \
219- patch ('batcontrol.core.solar_factory' ), \
220- patch ('batcontrol.core.consumption_factory' ):
221-
222- batcontrol = Batcontrol (mock_config )
199+ batcontrol = batcontrol_with_patched_factories
223200
224- # Test minimum boundary (0.0)
225- batcontrol .api_set_production_offset (0.0 )
226- assert batcontrol .production_offset_percent == 0.0
201+ batcontrol .api_set_production_offset (0.0 )
202+ assert batcontrol .production_offset_percent == 0.0
227203
228- # Test maximum boundary (2.0)
229- batcontrol .api_set_production_offset (2.0 )
230- assert batcontrol .production_offset_percent == 2.0
204+ batcontrol .api_set_production_offset (2.0 )
205+ assert batcontrol .production_offset_percent == 2.0
231206
232- # Test normal value (1.0 = 100%)
233- batcontrol .api_set_production_offset (1.0 )
234- assert batcontrol .production_offset_percent == 1.0
207+ batcontrol .api_set_production_offset (1.0 )
208+ assert batcontrol .production_offset_percent == 1.0
235209
236210
237211class TestProductionOffsetMqtt :
0 commit comments