import asyncio from types import SimpleNamespace from app.auto.config import ( TemperatureConfig, ) from app.auto.controller import ( AutoController, ) from app.auto.temperature_policy import ( TemperaturePolicy, ) class FakeQingpingService: def __init__(self): self.online = True self.state = SimpleNamespace( temperature=19.4, ) class FakeTionController: def __init__(self): self.calls = [] async def heater_on(self): self.calls.append( ("heater_on", None) ) async def heater_off(self): self.calls.append( ("heater_off", None) ) class FakeTionService: def __init__(self): self.online = True self.state = SimpleNamespace( in_temp=18, ) self.controller = ( FakeTionController() ) async def execute( self, operation, ): return await operation( self.controller ) def make_resolution( target_temp=20.0, ): return SimpleNamespace( scheduled_settings=( SimpleNamespace( target_temp=target_temp, ) ) ) def make_controller(): qingping = ( FakeQingpingService() ) tion = FakeTionService() policy = TemperaturePolicy( TemperatureConfig( hysteresis=0.5, ) ) controller = AutoController( schedule_service=None, qingping_service=qingping, tion_service=tion, temperature_policy=policy, ) return ( controller, qingping, tion, ) async def test_qingping_heating(): controller, qingping, tion = ( make_controller() ) resolution = make_resolution() # 19.4 < 19.5 # Heater должен включиться. await controller._process_heater( resolution ) assert tion.controller.calls == [ ("heater_on", None) ] assert ( controller._auto_heater is True ) assert ( controller._temperature == 19.4 ) assert ( controller._temperature_source == "qingping" ) tion.controller.calls.clear() # ---------------------------------------------- # 19.8 # # Heater уже ON. # До 20 градусов ещё не дошли. # Продолжаем греть. # # Новую команду отправлять не нужно. # ---------------------------------------------- qingping.state.temperature = 19.8 await controller._process_heater( resolution ) assert tion.controller.calls == [] assert ( controller._auto_heater is True ) # ---------------------------------------------- # 20.0 # # Цель достигнута. # ---------------------------------------------- qingping.state.temperature = 20.0 await controller._process_heater( resolution ) assert tion.controller.calls == [ ("heater_off", None) ] assert ( controller._auto_heater is False ) async def test_tion_temperature_fallback(): controller, qingping, tion = ( make_controller() ) resolution = make_resolution() # Qingping отключился. qingping.online = False # Используем Tion. tion.state.in_temp = 19 await controller._process_heater( resolution ) assert tion.controller.calls == [ ("heater_on", None) ] assert ( controller._temperature == 19 ) assert ( controller._temperature_source == "tion" ) assert ( controller._auto_heater is True ) async def test_no_temperature(): controller, qingping, tion = ( make_controller() ) resolution = make_resolution() qingping.online = False tion.online = False await controller._process_heater( resolution ) assert tion.controller.calls == [ ("heater_off", None) ] assert ( controller._temperature is None ) assert ( controller._temperature_source is None ) assert ( controller._auto_heater is False ) async def test_target_temperature_missing(): controller, qingping, tion = ( make_controller() ) resolution = make_resolution( target_temp=None ) await controller._process_heater( resolution ) assert tion.controller.calls == [ ("heater_off", None) ] assert ( controller._auto_heater is False ) async def main(): await test_qingping_heating() await test_tion_temperature_fallback() await test_no_temperature() await test_target_temperature_missing() print() print("=" * 70) print( "ALL AUTO HEATER PROCESS " "TESTS PASSED" ) print("=" * 70) if __name__ == "__main__": asyncio.run(main())