From 81780bd3160384ac21fc48c3164704c213e7740d Mon Sep 17 00:00:00 2001 From: Jean-Marc Collin Date: Sun, 24 Nov 2024 16:23:14 +0000 Subject: [PATCH] With testu for config_flow ok --- .../versatile_thermostat/config_flow.py | 9 +- .../thermostat_climate_valve.py | 10 +- tests/test_config_flow.py | 336 ++++++++++++++++++ 3 files changed, 350 insertions(+), 5 deletions(-) diff --git a/custom_components/versatile_thermostat/config_flow.py b/custom_components/versatile_thermostat/config_flow.py index b8404f4..582d786 100644 --- a/custom_components/versatile_thermostat/config_flow.py +++ b/custom_components/versatile_thermostat/config_flow.py @@ -147,7 +147,11 @@ class VersatileThermostatBaseConfigFlow(FlowHandler): def check_valve_regulation_nb_entities(self, data: dict, step_id=None) -> bool: """Check the number of entities for Valve regulation""" - underlyings_to_check = data if step_id == "type" else self._infos + if step_id not in ["type", "valve_regulation", "check_complete"]: + return True + + # underlyings_to_check = data if step_id == "type" else self._infos + underlyings_to_check = self._infos # data if step_id == "type" else self._infos regulation_infos_to_check = ( data if step_id == "valve_regulation" else self._infos ) @@ -348,7 +352,7 @@ class VersatileThermostatBaseConfigFlow(FlowHandler): ): return False - if not self.check_valve_regulation_nb_entities(infos): + if not self.check_valve_regulation_nb_entities(infos, "check_complete"): return False return True @@ -444,6 +448,7 @@ class VersatileThermostatBaseConfigFlow(FlowHandler): if ( self._infos.get(CONF_PROP_FUNCTION) == PROPORTIONAL_FUNCTION_TPI or is_central_config + or self.is_valve_regulation_selected(self._infos) ): menu_options.append("tpi") diff --git a/custom_components/versatile_thermostat/thermostat_climate_valve.py b/custom_components/versatile_thermostat/thermostat_climate_valve.py index c64305b..de4da04 100644 --- a/custom_components/versatile_thermostat/thermostat_climate_valve.py +++ b/custom_components/versatile_thermostat/thermostat_climate_valve.py @@ -86,10 +86,14 @@ class ThermostatOverClimateValve(ThermostatOverClimate): self.name, ) + offset_list = config_entry.get(CONF_OFFSET_CALIBRATION_LIST) + opening_list = config_entry.get(CONF_OPENING_DEGREE_LIST) + closing_list = config_entry.get(CONF_CLOSING_DEGREE_LIST) for idx, _ in enumerate(config_entry.get(CONF_UNDERLYING_LIST)): - offset = config_entry.get(CONF_OFFSET_CALIBRATION_LIST)[idx] - opening = config_entry.get(CONF_OPENING_DEGREE_LIST)[idx] - closing = config_entry.get(CONF_CLOSING_DEGREE_LIST)[idx] + offset = offset_list[idx] if idx < len(offset_list) else None + # number of opening should equal number of underlying + opening = opening_list[idx] + closing = closing_list[idx] if idx < len(closing_list) else None under = UnderlyingValveRegulation( hass=self._hass, thermostat=self, diff --git a/tests/test_config_flow.py b/tests/test_config_flow.py index 448dd79..d73b501 100644 --- a/tests/test_config_flow.py +++ b/tests/test_config_flow.py @@ -1385,3 +1385,339 @@ async def test_user_config_flow_over_switch_bug_552_tpi( assert result["result"].version == 2 assert result["result"].title == "TheOverSwitchMockName" assert isinstance(result["result"], ConfigEntry) + + +# @pytest.mark.parametrize("expected_lingering_tasks", [True]) +# @pytest.mark.parametrize("expected_lingering_timers", [True]) +# @pytest.mark.skip +async def test_user_config_flow_over_climate_valve( + hass: HomeAssistant, skip_hass_states_get +): # pylint: disable=unused-argument + """Test the config flow with all thermostat_over_climate with the valve regulation activated. + We don't use any features nor central config + but we will add multiple underlying climate and valve""" + + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": SOURCE_USER} + ) + assert result["type"] == FlowResultType.FORM + assert result["step_id"] == SOURCE_USER + + # 1. Type + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + user_input={ + CONF_THERMOSTAT_TYPE: CONF_THERMOSTAT_CLIMATE, + }, + ) + assert result["type"] == FlowResultType.MENU + assert result["step_id"] == "menu" + assert result["menu_options"] == [ + "main", + "features", + "type", + "presets", + "advanced", + "configuration_not_complete", + ] + assert result.get("errors") is None + + # 2. Main + result = await hass.config_entries.flow.async_configure( + result["flow_id"], user_input={"next_step_id": "main"} + ) + assert result["type"] == FlowResultType.FORM + assert result["step_id"] == "main" + assert result.get("errors") == {} + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + user_input={ + CONF_NAME: "TheOverClimateMockName", + CONF_TEMP_SENSOR: "sensor.mock_temp_sensor", + CONF_CYCLE_MIN: 5, + CONF_DEVICE_POWER: 1, + CONF_USE_MAIN_CENTRAL_CONFIG: False, + CONF_USE_CENTRAL_MODE: False, + # Keep default values which are False + }, + ) + assert result["type"] == FlowResultType.FORM + assert result["step_id"] == "main" + assert result.get("errors") == {} + + # 3. Main 2 + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + user_input={ + CONF_EXTERNAL_TEMP_SENSOR: "sensor.mock_ext_temp_sensor", + CONF_TEMP_MIN: 15, + CONF_TEMP_MAX: 30, + CONF_STEP_TEMPERATURE: 0.1, + # Keep default values which are False + }, + ) + assert result["type"] == FlowResultType.MENU + assert result["step_id"] == "menu" + assert result.get("errors") is None + + # 4. Type + result = await hass.config_entries.flow.async_configure( + result["flow_id"], user_input={"next_step_id": "type"} + ) + assert result["type"] == FlowResultType.FORM + assert result["step_id"] == "type" + assert result.get("errors") == {} + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + user_input={ + CONF_UNDERLYING_LIST: ["climate.mock_climate1", "climate.mock_climate2"], + CONF_AC_MODE: False, + CONF_AUTO_REGULATION_MODE: CONF_AUTO_REGULATION_VALVE, + CONF_AUTO_REGULATION_DTEMP: 0.5, + CONF_AUTO_REGULATION_PERIOD_MIN: 2, + CONF_AUTO_FAN_MODE: CONF_AUTO_FAN_HIGH, + CONF_AUTO_REGULATION_USE_DEVICE_TEMP: False, + }, + ) + assert result["type"] == FlowResultType.MENU + assert result["step_id"] == "menu" + assert result["menu_options"] == [ + "main", + "features", + "type", + "tpi", + "presets", + "valve_regulation", + "advanced", + "configuration_not_complete", + # "finalize", # because we need Advanced default parameters + ] + assert result.get("errors") is None + + # 5. TPI + result = await hass.config_entries.flow.async_configure( + result["flow_id"], user_input={"next_step_id": "tpi"} + ) + assert result["type"] == FlowResultType.FORM + assert result["step_id"] == "tpi" + assert result.get("errors") == {} + + # 6. TPI 2 + result = await hass.config_entries.flow.async_configure( + result["flow_id"], user_input={CONF_USE_TPI_CENTRAL_CONFIG: False} + ) + + assert result["type"] == FlowResultType.FORM + assert result["step_id"] == "tpi" + assert result.get("errors") == {} + + # 7. Menu + result = await hass.config_entries.flow.async_configure( + result["flow_id"], user_input=MOCK_TH_OVER_SWITCH_TPI_CONFIG + ) + + # 8. Presets + result = await hass.config_entries.flow.async_configure( + result["flow_id"], user_input={"next_step_id": "presets"} + ) + assert result["type"] == FlowResultType.FORM + assert result["step_id"] == "presets" + assert result.get("errors") == {} + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], user_input={CONF_USE_PRESETS_CENTRAL_CONFIG: False} + ) + assert result["type"] == FlowResultType.MENU + assert result["step_id"] == "menu" + assert result.get("errors") is None + + # 9. Features + result = await hass.config_entries.flow.async_configure( + result["flow_id"], user_input={"next_step_id": "features"} + ) + assert result["type"] == FlowResultType.FORM + assert result["step_id"] == "features" + assert result.get("errors") == {} + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + user_input={ + CONF_USE_MOTION_FEATURE: False, + CONF_USE_POWER_FEATURE: False, + CONF_USE_PRESENCE_FEATURE: False, + CONF_USE_WINDOW_FEATURE: False, + CONF_USE_AUTO_START_STOP_FEATURE: False, + }, + ) + assert result["type"] == FlowResultType.MENU + assert result["step_id"] == "menu" + assert result.get("errors") is None + assert result["menu_options"] == [ + "main", + "features", + "type", + "tpi", + "presets", + "valve_regulation", + "advanced", + "configuration_not_complete", + # "finalize", finalize is not present waiting for advanced configuration + ] + + # 11. Valve_regulation + result = await hass.config_entries.flow.async_configure( + result["flow_id"], user_input={"next_step_id": "valve_regulation"} + ) + assert result["type"] == FlowResultType.FORM + assert result["step_id"] == "valve_regulation" + assert result.get("errors") == {} + + # 11.1 Only one but 2 expected + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + user_input={ + CONF_OFFSET_CALIBRATION_LIST: ["number.offset_calibration1"], + CONF_OPENING_DEGREE_LIST: ["number.opening_degree1"], + CONF_CLOSING_DEGREE_LIST: ["number.closing_degree1"], + }, + ) + assert result["type"] == FlowResultType.FORM + assert result["step_id"] == "valve_regulation" + assert result.get("errors") == {"base": "valve_regulation_nb_entities_incorrect"} + + # 11.2 Give two openings but only one offset_calibration + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + user_input={ + CONF_OFFSET_CALIBRATION_LIST: [ + "number.offset_calibration1", + "number.offset_calibration2", + ], + CONF_OPENING_DEGREE_LIST: [ + "number.opening_degree1", + "number.opening_degree2", + ], + CONF_CLOSING_DEGREE_LIST: ["number.closing_degree1"], + }, + ) + assert result["type"] == FlowResultType.FORM + assert result["step_id"] == "valve_regulation" + assert result.get("errors") == {"base": "valve_regulation_nb_entities_incorrect"} + + # 11.3 Give two openings and 2 calibration and 0 closing + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + user_input={ + CONF_OFFSET_CALIBRATION_LIST: [ + "number.offset_calibration1", + "number.offset_calibration2", + ], + CONF_OPENING_DEGREE_LIST: [ + "number.opening_degree1", + "number.opening_degree2", + ], + CONF_CLOSING_DEGREE_LIST: [], + }, + ) + assert result["type"] == FlowResultType.MENU + assert result["step_id"] == "menu" + assert result.get("errors") is None + assert result["menu_options"] == [ + "main", + "features", + "type", + "tpi", + "presets", + "valve_regulation", + "advanced", + "configuration_not_complete", + # "finalize", finalize is not present waiting for advanced configuration + ] + + # 10. Advanced + result = await hass.config_entries.flow.async_configure( + result["flow_id"], user_input={"next_step_id": "advanced"} + ) + assert result["type"] == FlowResultType.FORM + assert result["step_id"] == "advanced" + assert result.get("errors") == {} + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + user_input={CONF_USE_ADVANCED_CENTRAL_CONFIG: False}, + ) + assert result["type"] == FlowResultType.FORM + assert result["step_id"] == "advanced" + assert result.get("errors") == {} + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + user_input={ + CONF_MINIMAL_ACTIVATION_DELAY: 10, + CONF_SECURITY_DELAY_MIN: 5, + CONF_SECURITY_MIN_ON_PERCENT: 0.4, + CONF_SECURITY_DEFAULT_ON_PERCENT: 0.3, + }, + ) + assert result["type"] == FlowResultType.MENU + assert result["step_id"] == "menu" + assert result.get("errors") is None + assert result["menu_options"] == [ + "main", + "features", + "type", + "tpi", + "presets", + "valve_regulation", + "advanced", + "finalize", # Now it is complete + ] + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], user_input={"next_step_id": "finalize"} + ) + assert result["type"] == FlowResultType.CREATE_ENTRY + assert result.get("errors") is None + assert result[ + "data" + ] == MOCK_TH_OVER_CLIMATE_USER_CONFIG | MOCK_TH_OVER_CLIMATE_MAIN_CONFIG | MOCK_TH_OVER_CLIMATE_CENTRAL_MAIN_CONFIG | MOCK_TH_OVER_CLIMATE_TYPE_CONFIG | { + CONF_MINIMAL_ACTIVATION_DELAY: 10, + CONF_SECURITY_DELAY_MIN: 5, + CONF_SECURITY_MIN_ON_PERCENT: 0.4, + CONF_SECURITY_DEFAULT_ON_PERCENT: 0.3, + } | MOCK_DEFAULT_FEATURE_CONFIG | { + CONF_USE_MAIN_CENTRAL_CONFIG: False, + CONF_USE_PRESETS_CENTRAL_CONFIG: False, + CONF_USE_MOTION_FEATURE: False, + CONF_USE_POWER_FEATURE: False, + CONF_USE_PRESENCE_FEATURE: False, + CONF_USE_WINDOW_FEATURE: False, + CONF_USE_AUTO_START_STOP_FEATURE: False, + CONF_USE_CENTRAL_BOILER_FEATURE: False, + CONF_USE_TPI_CENTRAL_CONFIG: False, + CONF_USE_WINDOW_CENTRAL_CONFIG: False, + CONF_USE_MOTION_CENTRAL_CONFIG: False, + CONF_USE_POWER_CENTRAL_CONFIG: False, + CONF_USE_PRESENCE_CENTRAL_CONFIG: False, + CONF_USE_ADVANCED_CENTRAL_CONFIG: False, + CONF_USED_BY_CENTRAL_BOILER: False, + CONF_USE_CENTRAL_MODE: False, + CONF_AUTO_REGULATION_MODE: CONF_AUTO_REGULATION_VALVE, + CONF_UNDERLYING_LIST: ["climate.mock_climate1", "climate.mock_climate2"], + CONF_OPENING_DEGREE_LIST: ["number.opening_degree1", "number.opening_degree2"], + CONF_CLOSING_DEGREE_LIST: [], + CONF_OFFSET_CALIBRATION_LIST: [ + "number.offset_calibration1", + "number.offset_calibration2", + ], + CONF_PROP_FUNCTION: PROPORTIONAL_FUNCTION_TPI, + CONF_TPI_COEF_INT: 0.3, + CONF_TPI_COEF_EXT: 0.1, + } + assert result["result"] + assert result["result"].domain == DOMAIN + assert result["result"].version == 2 + assert result["result"].title == "TheOverClimateMockName" + assert isinstance(result["result"], ConfigEntry)