diff --git a/homeassistant/components/zwave_js/config_flow.py b/homeassistant/components/zwave_js/config_flow.py index 20ebe94c00e..1337331bfb6 100644 --- a/homeassistant/components/zwave_js/config_flow.py +++ b/homeassistant/components/zwave_js/config_flow.py @@ -23,7 +23,6 @@ from homeassistant.config_entries import ( SOURCE_USB, ConfigEntry, ConfigEntryBaseFlow, - ConfigEntryState, ConfigFlow, ConfigFlowResult, OptionsFlow, @@ -787,7 +786,21 @@ class OptionsFlowHandler(BaseZwaveJSFlow, OptionsFlow): {CONF_USE_ADDON: self.config_entry.data.get(CONF_USE_ADDON, True)} ), ) + if not user_input[CONF_USE_ADDON]: + if self.config_entry.data.get(CONF_USE_ADDON): + # Unload the config entry before stopping the add-on. + await self.hass.config_entries.async_unload(self.config_entry.entry_id) + addon_manager = get_addon_manager(self.hass) + _LOGGER.debug("Stopping Z-Wave JS add-on") + try: + await addon_manager.async_stop_addon() + except AddonError as err: + _LOGGER.error(err) + self.hass.config_entries.async_schedule_reload( + self.config_entry.entry_id + ) + raise AbortFlow("addon_stop_failed") from err return await self.async_step_manual() addon_info = await self._async_get_addon_info() @@ -840,10 +853,7 @@ class OptionsFlowHandler(BaseZwaveJSFlow, OptionsFlow): if addon_info.state == AddonState.RUNNING and not self.restart_addon: return await self.async_step_finish_addon_setup() - if ( - self.config_entry.data.get(CONF_USE_ADDON) - and self.config_entry.state == ConfigEntryState.LOADED - ): + if self.config_entry.data.get(CONF_USE_ADDON): # Disconnect integration before restarting add-on. await self.hass.config_entries.async_unload(self.config_entry.entry_id) diff --git a/homeassistant/components/zwave_js/strings.json b/homeassistant/components/zwave_js/strings.json index 8f23fee4447..644d829b032 100644 --- a/homeassistant/components/zwave_js/strings.json +++ b/homeassistant/components/zwave_js/strings.json @@ -214,6 +214,7 @@ "addon_install_failed": "[%key:component::zwave_js::config::abort::addon_install_failed%]", "addon_set_config_failed": "[%key:component::zwave_js::config::abort::addon_set_config_failed%]", "addon_start_failed": "[%key:component::zwave_js::config::abort::addon_start_failed%]", + "addon_stop_failed": "Failed to stop the Z-Wave add-on.", "already_configured": "[%key:common::config_flow::abort::already_configured_device%]", "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]", "different_device": "The connected USB device is not the same as previously configured for this config entry. Please instead create a new config entry for the new device." diff --git a/tests/components/zwave_js/test_config_flow.py b/tests/components/zwave_js/test_config_flow.py index f62ae9c740b..fe1a665c4b0 100644 --- a/tests/components/zwave_js/test_config_flow.py +++ b/tests/components/zwave_js/test_config_flow.py @@ -70,6 +70,15 @@ def setup_entry_fixture() -> Generator[AsyncMock]: yield mock_setup_entry +@pytest.fixture(name="unload_entry") +def unload_entry_fixture() -> Generator[AsyncMock]: + """Mock entry unload.""" + with patch( + "homeassistant.components.zwave_js.async_unload_entry", return_value=True + ) as mock_unload_entry: + yield mock_unload_entry + + @pytest.fixture(name="supervisor") def mock_supervisor_fixture() -> Generator[None]: """Mock Supervisor.""" @@ -2038,6 +2047,104 @@ async def test_options_not_addon( assert client.disconnect.call_count == 1 +@pytest.mark.usefixtures("supervisor") +async def test_options_not_addon_with_addon( + hass: HomeAssistant, + setup_entry: AsyncMock, + unload_entry: AsyncMock, + integration: MockConfigEntry, + stop_addon: AsyncMock, +) -> None: + """Test options flow opting out of add-on on Supervisor with add-on.""" + entry = integration + hass.config_entries.async_update_entry( + entry, + data={**entry.data, "url": "ws://host1:3001", "use_addon": True}, + unique_id="1234", + ) + + assert entry.state is config_entries.ConfigEntryState.LOADED + assert unload_entry.call_count == 0 + setup_entry.reset_mock() + + result = await hass.config_entries.options.async_init(entry.entry_id) + + assert result["type"] is FlowResultType.FORM + assert result["step_id"] == "on_supervisor" + + result = await hass.config_entries.options.async_configure( + result["flow_id"], {"use_addon": False} + ) + + assert entry.state is config_entries.ConfigEntryState.NOT_LOADED + assert setup_entry.call_count == 0 + assert unload_entry.call_count == 1 + assert stop_addon.call_count == 1 + assert stop_addon.call_args == call("core_zwave_js") + + assert result["type"] is FlowResultType.FORM + assert result["step_id"] == "manual" + + result = await hass.config_entries.options.async_configure( + result["flow_id"], + { + "url": "ws://localhost:3000", + }, + ) + await hass.async_block_till_done() + + assert result["type"] is FlowResultType.CREATE_ENTRY + assert entry.data["url"] == "ws://localhost:3000" + assert entry.data["use_addon"] is False + assert entry.data["integration_created_addon"] is False + assert entry.state is config_entries.ConfigEntryState.LOADED + assert setup_entry.call_count == 1 + assert unload_entry.call_count == 1 + + +@pytest.mark.usefixtures("supervisor") +async def test_options_not_addon_with_addon_stop_fail( + hass: HomeAssistant, + setup_entry: AsyncMock, + unload_entry: AsyncMock, + integration: MockConfigEntry, + stop_addon: AsyncMock, +) -> None: + """Test options flow opting out of add-on and add-on stop error.""" + stop_addon.side_effect = SupervisorError("Boom!") + entry = integration + hass.config_entries.async_update_entry( + entry, + data={**entry.data, "url": "ws://host1:3001", "use_addon": True}, + unique_id="1234", + ) + + assert entry.state is config_entries.ConfigEntryState.LOADED + assert unload_entry.call_count == 0 + setup_entry.reset_mock() + + result = await hass.config_entries.options.async_init(entry.entry_id) + + assert result["type"] is FlowResultType.FORM + assert result["step_id"] == "on_supervisor" + + result = await hass.config_entries.options.async_configure( + result["flow_id"], {"use_addon": False} + ) + await hass.async_block_till_done() + + assert stop_addon.call_count == 1 + assert stop_addon.call_args == call("core_zwave_js") + + assert result["type"] is FlowResultType.ABORT + assert result["reason"] == "addon_stop_failed" + assert entry.data["url"] == "ws://host1:3001" + assert entry.data["use_addon"] is True + assert entry.state is config_entries.ConfigEntryState.LOADED + assert setup_entry.call_count == 1 + assert unload_entry.call_count == 1 + + @pytest.mark.parametrize( ( "discovery_info",