diff --git a/homeassistant/components/whirlpool/__init__.py b/homeassistant/components/whirlpool/__init__.py index 0d6ae706f0c..f335f2f5e01 100644 --- a/homeassistant/components/whirlpool/__init__.py +++ b/homeassistant/components/whirlpool/__init__.py @@ -5,14 +5,15 @@ import logging import aiohttp from whirlpool.appliancesmanager import AppliancesManager from whirlpool.auth import Auth -from whirlpool.backendselector import BackendSelector, Brand, Region +from whirlpool.backendselector import BackendSelector from homeassistant.config_entries import ConfigEntry -from homeassistant.const import Platform +from homeassistant.const import CONF_PASSWORD, CONF_REGION, CONF_USERNAME, Platform from homeassistant.core import HomeAssistant from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady -from .const import DOMAIN +from .const import CONF_REGIONS_MAP, DOMAIN +from .util import get_brand_for_region _LOGGER = logging.getLogger(__name__) @@ -23,8 +24,10 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Set up Whirlpool Sixth Sense from a config entry.""" hass.data.setdefault(DOMAIN, {}) - backend_selector = BackendSelector(Brand.Whirlpool, Region.EU) - auth = Auth(backend_selector, entry.data["username"], entry.data["password"]) + region = CONF_REGIONS_MAP[entry.data.get(CONF_REGION, "EU")] + brand = get_brand_for_region(region) + backend_selector = BackendSelector(brand, region) + auth = Auth(backend_selector, entry.data[CONF_USERNAME], entry.data[CONF_PASSWORD]) try: await auth.do_auth(store=False) except aiohttp.ClientError as ex: diff --git a/homeassistant/components/whirlpool/config_flow.py b/homeassistant/components/whirlpool/config_flow.py index 4a41c353d7f..9bd404214ad 100644 --- a/homeassistant/components/whirlpool/config_flow.py +++ b/homeassistant/components/whirlpool/config_flow.py @@ -9,18 +9,24 @@ from typing import Any import aiohttp import voluptuous as vol from whirlpool.auth import Auth -from whirlpool.backendselector import BackendSelector, Brand, Region +from whirlpool.backendselector import BackendSelector from homeassistant import config_entries, core, exceptions -from homeassistant.const import CONF_PASSWORD, CONF_USERNAME +from homeassistant.const import CONF_PASSWORD, CONF_REGION, CONF_USERNAME from homeassistant.data_entry_flow import FlowResult -from .const import DOMAIN +from .const import CONF_REGIONS_MAP, DOMAIN +from .util import get_brand_for_region _LOGGER = logging.getLogger(__name__) + STEP_USER_DATA_SCHEMA = vol.Schema( - {vol.Required(CONF_USERNAME): str, vol.Required(CONF_PASSWORD): str} + { + vol.Required(CONF_USERNAME): str, + vol.Required(CONF_PASSWORD): str, + vol.Required(CONF_REGION): vol.In(list(CONF_REGIONS_MAP)), + } ) REAUTH_SCHEMA = vol.Schema({vol.Required(CONF_PASSWORD): str}) @@ -33,7 +39,9 @@ async def validate_input( Data has the keys from STEP_USER_DATA_SCHEMA with values provided by the user. """ - backend_selector = BackendSelector(Brand.Whirlpool, Region.EU) + region = CONF_REGIONS_MAP[data[CONF_REGION]] + brand = get_brand_for_region(region) + backend_selector = BackendSelector(brand, region) auth = Auth(backend_selector, data[CONF_USERNAME], data[CONF_PASSWORD]) try: await auth.do_auth() @@ -68,7 +76,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): assert self.entry is not None password = user_input[CONF_PASSWORD] data = { - CONF_USERNAME: self.entry.data[CONF_USERNAME], + **self.entry.data, CONF_PASSWORD: password, } diff --git a/homeassistant/components/whirlpool/const.py b/homeassistant/components/whirlpool/const.py index 8a030d8fab2..8f6d1b93ca3 100644 --- a/homeassistant/components/whirlpool/const.py +++ b/homeassistant/components/whirlpool/const.py @@ -1,3 +1,10 @@ """Constants for the Whirlpool Sixth Sense integration.""" +from whirlpool.backendselector import Region + DOMAIN = "whirlpool" + +CONF_REGIONS_MAP = { + "EU": Region.EU, + "US": Region.US, +} diff --git a/homeassistant/components/whirlpool/manifest.json b/homeassistant/components/whirlpool/manifest.json index a7c99e9066c..fda9ba651b5 100644 --- a/homeassistant/components/whirlpool/manifest.json +++ b/homeassistant/components/whirlpool/manifest.json @@ -3,7 +3,7 @@ "name": "Whirlpool Sixth Sense", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/whirlpool", - "requirements": ["whirlpool-sixth-sense==0.17.0"], + "requirements": ["whirlpool-sixth-sense==0.17.1"], "codeowners": ["@abmantis"], "iot_class": "cloud_push", "loggers": ["whirlpool"] diff --git a/homeassistant/components/whirlpool/util.py b/homeassistant/components/whirlpool/util.py new file mode 100644 index 00000000000..9467064dc96 --- /dev/null +++ b/homeassistant/components/whirlpool/util.py @@ -0,0 +1,8 @@ +"""Utility functions for the Whirlpool Sixth Sense integration.""" + +from whirlpool.backendselector import Brand, Region + + +def get_brand_for_region(region: Region) -> bool: + """Get the correct brand for each region.""" + return Brand.Maytag if region == Region.US else Brand.Whirlpool diff --git a/requirements_all.txt b/requirements_all.txt index efdeceff9a9..ab4a3e9795b 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2567,7 +2567,7 @@ waterfurnace==1.1.0 webexteamssdk==1.1.1 # homeassistant.components.whirlpool -whirlpool-sixth-sense==0.17.0 +whirlpool-sixth-sense==0.17.1 # homeassistant.components.whois whois==0.9.16 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index a6fd5a6de10..1320a9fa5e9 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1792,7 +1792,7 @@ wallbox==0.4.12 watchdog==2.2.0 # homeassistant.components.whirlpool -whirlpool-sixth-sense==0.17.0 +whirlpool-sixth-sense==0.17.1 # homeassistant.components.whois whois==0.9.16 diff --git a/tests/components/whirlpool/__init__.py b/tests/components/whirlpool/__init__.py index 3f50518b4ad..d47fb5337fd 100644 --- a/tests/components/whirlpool/__init__.py +++ b/tests/components/whirlpool/__init__.py @@ -1,21 +1,29 @@ """Tests for the Whirlpool Sixth Sense integration.""" from homeassistant.components.whirlpool.const import DOMAIN -from homeassistant.const import CONF_PASSWORD, CONF_USERNAME +from homeassistant.const import CONF_PASSWORD, CONF_REGION, CONF_USERNAME from homeassistant.core import HomeAssistant from tests.common import MockConfigEntry -async def init_integration(hass: HomeAssistant) -> MockConfigEntry: +async def init_integration(hass: HomeAssistant, region: str = "EU") -> MockConfigEntry: """Set up the Whirlpool integration in Home Assistant.""" entry = MockConfigEntry( domain=DOMAIN, data={ CONF_USERNAME: "nobody", CONF_PASSWORD: "qwerty", + CONF_REGION: region, }, ) + return await init_integration_with_entry(hass, entry) + + +async def init_integration_with_entry( + hass: HomeAssistant, entry: MockConfigEntry +) -> MockConfigEntry: + """Set up the Whirlpool integration in Home Assistant.""" entry.add_to_hass(hass) await hass.config_entries.async_setup(entry.entry_id) await hass.async_block_till_done() diff --git a/tests/components/whirlpool/conftest.py b/tests/components/whirlpool/conftest.py index eba58b07faa..a5e044b51ca 100644 --- a/tests/components/whirlpool/conftest.py +++ b/tests/components/whirlpool/conftest.py @@ -5,11 +5,21 @@ from unittest.mock import AsyncMock import pytest import whirlpool import whirlpool.aircon +from whirlpool.backendselector import Brand, Region MOCK_SAID1 = "said1" MOCK_SAID2 = "said2" +@pytest.fixture( + name="region", + params=[("EU", Region.EU, Brand.Whirlpool), ("US", Region.US, Brand.Maytag)], +) +def fixture_region(request): + """Return a region for input.""" + return request.param + + @pytest.fixture(name="mock_auth_api") def fixture_mock_auth_api(): """Set up Auth fixture.""" @@ -33,6 +43,15 @@ def fixture_mock_appliances_manager_api(): yield mock_appliances_manager +@pytest.fixture(name="mock_backend_selector_api") +def fixture_mock_backend_selector_api(): + """Set up BackendSelector fixture.""" + with mock.patch( + "homeassistant.components.whirlpool.BackendSelector" + ) as mock_backend_selector: + yield mock_backend_selector + + def get_aircon_mock(said): """Get a mock of an air conditioner.""" mock_aircon = mock.Mock(said=said) diff --git a/tests/components/whirlpool/test_config_flow.py b/tests/components/whirlpool/test_config_flow.py index 6e188b68219..a65dbd928c3 100644 --- a/tests/components/whirlpool/test_config_flow.py +++ b/tests/components/whirlpool/test_config_flow.py @@ -13,8 +13,13 @@ from homeassistant.data_entry_flow import FlowResultType from tests.common import MockConfigEntry +CONFIG_INPUT = { + CONF_USERNAME: "test-username", + CONF_PASSWORD: "test-password", +} -async def test_form(hass): + +async def test_form(hass, region): """Test we get the form.""" result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_USER} @@ -27,15 +32,14 @@ async def test_form(hass): "homeassistant.components.whirlpool.config_flow.Auth.is_access_token_valid", return_value=True, ), patch( + "homeassistant.components.whirlpool.config_flow.BackendSelector" + ) as mock_backend_selector, patch( "homeassistant.components.whirlpool.async_setup_entry", return_value=True, ) as mock_setup_entry: result2 = await hass.config_entries.flow.async_configure( result["flow_id"], - { - "username": "test-username", - "password": "test-password", - }, + CONFIG_INPUT | {"region": region[0]}, ) await hass.async_block_till_done() @@ -44,11 +48,13 @@ async def test_form(hass): assert result2["data"] == { "username": "test-username", "password": "test-password", + "region": region[0], } assert len(mock_setup_entry.mock_calls) == 1 + mock_backend_selector.assert_called_once_with(region[2], region[1]) -async def test_form_invalid_auth(hass): +async def test_form_invalid_auth(hass, region): """Test we handle invalid auth.""" result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_USER} @@ -59,16 +65,13 @@ async def test_form_invalid_auth(hass): ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], - { - CONF_USERNAME: "test-username", - CONF_PASSWORD: "test-password", - }, + CONFIG_INPUT | {"region": region[0]}, ) assert result2["type"] == "form" assert result2["errors"] == {"base": "invalid_auth"} -async def test_form_cannot_connect(hass): +async def test_form_cannot_connect(hass, region): """Test we handle cannot connect error.""" result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_USER} @@ -79,16 +82,13 @@ async def test_form_cannot_connect(hass): ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], - { - CONF_USERNAME: "test-username", - CONF_PASSWORD: "test-password", - }, + CONFIG_INPUT | {"region": region[0]}, ) assert result2["type"] == "form" assert result2["errors"] == {"base": "cannot_connect"} -async def test_form_auth_timeout(hass): +async def test_form_auth_timeout(hass, region): """Test we handle auth timeout error.""" result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_USER} @@ -99,16 +99,13 @@ async def test_form_auth_timeout(hass): ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], - { - CONF_USERNAME: "test-username", - CONF_PASSWORD: "test-password", - }, + CONFIG_INPUT | {"region": region[0]}, ) assert result2["type"] == "form" assert result2["errors"] == {"base": "cannot_connect"} -async def test_form_generic_auth_exception(hass): +async def test_form_generic_auth_exception(hass, region): """Test we handle cannot connect error.""" result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_USER} @@ -119,16 +116,13 @@ async def test_form_generic_auth_exception(hass): ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], - { - CONF_USERNAME: "test-username", - CONF_PASSWORD: "test-password", - }, + CONFIG_INPUT | {"region": region[0]}, ) assert result2["type"] == "form" assert result2["errors"] == {"base": "unknown"} -async def test_form_already_configured(hass): +async def test_form_already_configured(hass, region): """Test we handle cannot connect error.""" mock_entry = MockConfigEntry( domain=DOMAIN, @@ -150,10 +144,7 @@ async def test_form_already_configured(hass): ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], - { - CONF_USERNAME: "test-username", - CONF_PASSWORD: "test-password", - }, + CONFIG_INPUT | {"region": region[0]}, ) await hass.async_block_till_done() @@ -161,12 +152,12 @@ async def test_form_already_configured(hass): assert result2["reason"] == "already_configured" -async def test_reauth_flow(hass: HomeAssistant) -> None: +async def test_reauth_flow(hass: HomeAssistant, region) -> None: """Test a successful reauth flow.""" mock_entry = MockConfigEntry( domain=DOMAIN, - data={CONF_USERNAME: "test-username", CONF_PASSWORD: "test-password"}, + data=CONFIG_INPUT | {"region": region[0]}, unique_id="test-username", ) mock_entry.add_to_hass(hass) @@ -178,7 +169,11 @@ async def test_reauth_flow(hass: HomeAssistant) -> None: "unique_id": mock_entry.unique_id, "entry_id": mock_entry.entry_id, }, - data={"username": "test-username", "password": "new-password"}, + data={ + "username": "test-username", + "password": "new-password", + "region": region[0], + }, ) assert result["step_id"] == "reauth_confirm" @@ -203,15 +198,16 @@ async def test_reauth_flow(hass: HomeAssistant) -> None: assert mock_entry.data == { CONF_USERNAME: "test-username", CONF_PASSWORD: "new-password", + "region": region[0], } -async def test_reauth_flow_auth_error(hass: HomeAssistant) -> None: +async def test_reauth_flow_auth_error(hass: HomeAssistant, region) -> None: """Test an authorization error reauth flow.""" mock_entry = MockConfigEntry( domain=DOMAIN, - data={"username": "test-username", "password": "test-password"}, + data=CONFIG_INPUT | {"region": region[0]}, unique_id="test-username", ) mock_entry.add_to_hass(hass) @@ -223,7 +219,11 @@ async def test_reauth_flow_auth_error(hass: HomeAssistant) -> None: "unique_id": mock_entry.unique_id, "entry_id": mock_entry.entry_id, }, - data={"username": "test-username", "password": "new-password"}, + data={ + "username": "test-username", + "password": "new-password", + "region": region[0], + }, ) assert result["step_id"] == "reauth_confirm" @@ -246,12 +246,12 @@ async def test_reauth_flow_auth_error(hass: HomeAssistant) -> None: assert result2["errors"] == {"base": "invalid_auth"} -async def test_reauth_flow_connnection_error(hass: HomeAssistant) -> None: +async def test_reauth_flow_connection_error(hass: HomeAssistant, region) -> None: """Test a connection error reauth flow.""" mock_entry = MockConfigEntry( domain=DOMAIN, - data={"username": "test-username", "password": "test-password"}, + data=CONFIG_INPUT | {"region": region[0]}, unique_id="test-username", ) mock_entry.add_to_hass(hass) @@ -263,7 +263,11 @@ async def test_reauth_flow_connnection_error(hass: HomeAssistant) -> None: "unique_id": mock_entry.unique_id, "entry_id": mock_entry.entry_id, }, - data={CONF_USERNAME: "test-username", CONF_PASSWORD: "new-password"}, + data={ + CONF_USERNAME: "test-username", + CONF_PASSWORD: "new-password", + "region": region[0], + }, ) assert result["step_id"] == "reauth_confirm" diff --git a/tests/components/whirlpool/test_init.py b/tests/components/whirlpool/test_init.py index 619c2c783b7..dedaa26e618 100644 --- a/tests/components/whirlpool/test_init.py +++ b/tests/components/whirlpool/test_init.py @@ -2,19 +2,44 @@ from unittest.mock import AsyncMock, MagicMock import aiohttp +from whirlpool.backendselector import Brand, Region from homeassistant.components.whirlpool.const import DOMAIN from homeassistant.config_entries import ConfigEntryState +from homeassistant.const import CONF_PASSWORD, CONF_USERNAME from homeassistant.core import HomeAssistant -from . import init_integration +from . import init_integration, init_integration_with_entry + +from tests.common import MockConfigEntry -async def test_setup(hass: HomeAssistant): +async def test_setup(hass: HomeAssistant, mock_backend_selector_api: MagicMock, region): """Test setup.""" - entry = await init_integration(hass) + entry = await init_integration(hass, region[0]) assert len(hass.config_entries.async_entries(DOMAIN)) == 1 assert entry.state is ConfigEntryState.LOADED + mock_backend_selector_api.assert_called_once_with(region[2], region[1]) + + +async def test_setup_region_fallback( + hass: HomeAssistant, mock_backend_selector_api: MagicMock +): + """Test setup when no region is available on the ConfigEntry. + + This can happen after a version update, since there was no region in the first versions. + """ + entry = MockConfigEntry( + domain=DOMAIN, + data={ + CONF_USERNAME: "nobody", + CONF_PASSWORD: "qwerty", + }, + ) + entry = await init_integration_with_entry(hass, entry) + assert len(hass.config_entries.async_entries(DOMAIN)) == 1 + assert entry.state is ConfigEntryState.LOADED + mock_backend_selector_api.assert_called_once_with(Brand.Whirlpool, Region.EU) async def test_setup_http_exception(hass: HomeAssistant, mock_auth_api: MagicMock):