mirror of
https://github.com/home-assistant/core.git
synced 2025-07-24 05:37:44 +00:00
Add support for US in the Whirlpool integration (#77237)
* Support US region in the Whirlpool integration * Force maytag brand for US region * Add missing util.py file * Fix import after merge * run black * Missing region key in config flow test * Fixed Generic config entry * fixed typos in dict * Remove redundant list const Co-authored-by: mkmer <mike.j.kasper@gmail.com>
This commit is contained in:
parent
7aadcc1f97
commit
0e8164c07a
@ -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:
|
||||
|
@ -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,
|
||||
}
|
||||
|
||||
|
@ -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,
|
||||
}
|
||||
|
@ -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"]
|
||||
|
8
homeassistant/components/whirlpool/util.py
Normal file
8
homeassistant/components/whirlpool/util.py
Normal file
@ -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
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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()
|
||||
|
@ -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)
|
||||
|
@ -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"
|
||||
|
@ -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):
|
||||
|
Loading…
x
Reference in New Issue
Block a user