From e7b97f8265ad2e950bbeb1b901daf88846dbb825 Mon Sep 17 00:00:00 2001 From: Jose Santiago Date: Thu, 27 Nov 2025 22:42:39 -0500 Subject: [PATCH 1/2] Fix reconfiguration workflow and remove beautifulsoup package since we're no longer parsing html for integration --- custom_components/generac/config_flow.py | 41 ++++++++++++---- custom_components/generac/manifest.json | 2 +- package-lock.json | 6 +++ requirements.txt | 1 - tests/test_config_flow.py | 60 ++++++++++++++++++++++-- 5 files changed, 95 insertions(+), 15 deletions(-) create mode 100644 package-lock.json diff --git a/custom_components/generac/config_flow.py b/custom_components/generac/config_flow.py index 3643754..b9a79f3 100644 --- a/custom_components/generac/config_flow.py +++ b/custom_components/generac/config_flow.py @@ -45,11 +45,38 @@ def _extract_email_from_cookie(self, cookie_str): except Exception: return None - async def async_step_reconfigure( - self, user_input=None - ): # pylint: disable=unused-argument - """Manage the options.""" - return await self.async_step_user(user_input) + async def async_step_reconfigure(self, user_input=None): + """Handle reconfiguration.""" + errors = {} + entry = self.hass.config_entries.async_get_entry(self.context["entry_id"]) + + if user_input is not None: + session_cookie = user_input.get(CONF_SESSION_COOKIE, "") + error = await self._test_credentials( + "", + "", + session_cookie, + ) + if error is None: + return self.async_update_reload_and_abort( + entry, + data={**entry.data, **user_input}, + reason="reconfigure_successful", + ) + errors["base"] = error + + return self.async_show_form( + step_id="reconfigure", + data_schema=vol.Schema( + { + vol.Required( + CONF_SESSION_COOKIE, + default=entry.data.get(CONF_SESSION_COOKIE), + ): str, + } + ), + errors=errors, + ) async def async_step_user(self, user_input=None): """Handle a flow initialized by the user.""" @@ -71,9 +98,7 @@ async def async_step_user(self, user_input=None): unique_id = self._extract_email_from_cookie(session_cookie) or "generac" await self.async_set_unique_id(unique_id) - # We don't want to abort if the unique ID is already configured - # HA does the right thing and will reconfigure the existing entry - # self._abort_if_unique_id_configured() + self._abort_if_unique_id_configured() return self.async_create_entry(title=unique_id, data=user_input) else: diff --git a/custom_components/generac/manifest.json b/custom_components/generac/manifest.json index 0e2c21f..0d78bb7 100644 --- a/custom_components/generac/manifest.json +++ b/custom_components/generac/manifest.json @@ -7,6 +7,6 @@ "documentation": "https://github.com/binarydev/ha-generac", "iot_class": "cloud_polling", "issue_tracker": "https://github.com/binarydev/ha-generac/issues", - "requirements": ["beautifulsoup4==4.12.2", "dacite==1.8.1"], + "requirements": ["dacite==1.8.1"], "version": "0.4.0" } diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..7a07ca6 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,6 @@ +{ + "name": "ha-generac", + "lockfileVersion": 3, + "requires": true, + "packages": {} +} diff --git a/requirements.txt b/requirements.txt index 84100de..c56a1d1 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,3 @@ -beautifulsoup4==4.13.4 dacite==1.9.2 homeassistant==2025.8.3 voluptuous==0.15.2 diff --git a/tests/test_config_flow.py b/tests/test_config_flow.py index 2bc0428..dc8f0b4 100644 --- a/tests/test_config_flow.py +++ b/tests/test_config_flow.py @@ -169,19 +169,69 @@ async def test_options_flow(hass: HomeAssistant) -> None: @pytest.mark.asyncio async def test_reconfigure_flow(hass: HomeAssistant) -> None: """Test the reconfigure flow.""" - entry = MockConfigEntry(domain=DOMAIN, data={}, options={}) + entry = MockConfigEntry( + domain=DOMAIN, data={"session_cookie": "old_cookie"}, options={} + ) entry.add_to_hass(hass) - await hass.config_entries.async_setup(entry.entry_id) - await hass.async_block_till_done() + with patch("custom_components.generac.async_setup_entry", return_value=True): + await hass.config_entries.async_setup(entry.entry_id) + await hass.async_block_till_done() result = await hass.config_entries.flow.async_init( DOMAIN, context={ - "source": config_entries.SOURCE_RECONFIGURE, + "source": "reconfigure", "entry_id": entry.entry_id, }, ) assert result["type"] == "form" - assert result["step_id"] == "user" + assert result["step_id"] == "reconfigure" + + with patch( + "custom_components.generac.config_flow.GeneracApiClient.async_get_data", + return_value=True, + ), patch("custom_components.generac.async_setup_entry", return_value=True), patch( + "custom_components.generac.async_unload_entry", return_value=True + ): + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + { + "session_cookie": "new_cookie", + }, + ) + await hass.async_block_till_done() + + assert result2["type"] == "abort" + assert result2["reason"] == "reconfigure_successful" + assert entry.data["session_cookie"] == "new_cookie" + + +async def test_duplicate_entry(hass: HomeAssistant) -> None: + """Test duplicate entry is handled.""" + entry = MockConfigEntry( + domain=DOMAIN, + unique_id="binarydev@testing.com", + data={"session_cookie": "existing"}, + ) + entry.add_to_hass(hass) + + await setup.async_setup_component(hass, "persistent_notification", {}) + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + + with patch( + "custom_components.generac.config_flow.GeneracApiClient.async_get_data", + return_value=True, + ): + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + { + "session_cookie": "MobileLinkClientCookie=%7B%0D%0A%20%20%22signInName%22%3A%20%22binarydev%40testing.com%22%0D%0A%7D", + }, + ) + + assert result2["type"] == "abort" + assert result2["reason"] == "already_configured" From b6137b82897687060bd21d62da3e37ee58b6fe11 Mon Sep 17 00:00:00 2001 From: Jose Santiago Date: Thu, 27 Nov 2025 23:06:47 -0500 Subject: [PATCH 2/2] Humanize success message and address deprecation warning --- custom_components/generac/config_flow.py | 3 +-- tests/test_config_flow.py | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/custom_components/generac/config_flow.py b/custom_components/generac/config_flow.py index b9a79f3..1b2ff40 100644 --- a/custom_components/generac/config_flow.py +++ b/custom_components/generac/config_flow.py @@ -61,7 +61,7 @@ async def async_step_reconfigure(self, user_input=None): return self.async_update_reload_and_abort( entry, data={**entry.data, **user_input}, - reason="reconfigure_successful", + reason="Reconfigure Successful", ) errors["base"] = error @@ -147,7 +147,6 @@ class GeneracOptionsFlowHandler(config_entries.OptionsFlow): def __init__(self, config_entry): """Initialize HACS options flow.""" - self.config_entry = config_entry self.options = dict(config_entry.options) async def async_step_init(self, user_input=None): # pylint: disable=unused-argument diff --git a/tests/test_config_flow.py b/tests/test_config_flow.py index dc8f0b4..4dbd8fd 100644 --- a/tests/test_config_flow.py +++ b/tests/test_config_flow.py @@ -204,7 +204,7 @@ async def test_reconfigure_flow(hass: HomeAssistant) -> None: await hass.async_block_till_done() assert result2["type"] == "abort" - assert result2["reason"] == "reconfigure_successful" + assert result2["reason"] == "Reconfigure Successful" assert entry.data["session_cookie"] == "new_cookie"