Remove YAML support from August (#47615)

This commit is contained in:
J. Nick Koston 2021-03-16 13:22:07 -10:00 committed by GitHub
parent d49a436573
commit f605a3c149
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 239 additions and 299 deletions

View file

@ -4,169 +4,25 @@ import itertools
import logging import logging
from aiohttp import ClientError, ClientResponseError from aiohttp import ClientError, ClientResponseError
from august.authenticator import ValidationResult
from august.exceptions import AugustApiAIOHTTPError from august.exceptions import AugustApiAIOHTTPError
import voluptuous as vol
from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.const import ( from homeassistant.const import CONF_PASSWORD, HTTP_UNAUTHORIZED
CONF_PASSWORD,
CONF_TIMEOUT,
CONF_USERNAME,
HTTP_UNAUTHORIZED,
)
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryNotReady, HomeAssistantError from homeassistant.exceptions import ConfigEntryNotReady, HomeAssistantError
import homeassistant.helpers.config_validation as cv
from .activity import ActivityStream from .activity import ActivityStream
from .const import ( from .const import DATA_AUGUST, DOMAIN, MIN_TIME_BETWEEN_DETAIL_UPDATES, PLATFORMS
CONF_ACCESS_TOKEN_CACHE_FILE,
CONF_INSTALL_ID,
CONF_LOGIN_METHOD,
DATA_AUGUST,
DEFAULT_AUGUST_CONFIG_FILE,
DEFAULT_NAME,
DEFAULT_TIMEOUT,
DOMAIN,
LOGIN_METHODS,
MIN_TIME_BETWEEN_DETAIL_UPDATES,
PLATFORMS,
VERIFICATION_CODE_KEY,
)
from .exceptions import CannotConnect, InvalidAuth, RequireValidation from .exceptions import CannotConnect, InvalidAuth, RequireValidation
from .gateway import AugustGateway from .gateway import AugustGateway
from .subscriber import AugustSubscriberMixin from .subscriber import AugustSubscriberMixin
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
TWO_FA_REVALIDATE = "verify_configurator"
CONFIG_SCHEMA = vol.Schema(
vol.All(
cv.deprecated(DOMAIN),
{
DOMAIN: vol.Schema(
{
vol.Required(CONF_LOGIN_METHOD): vol.In(LOGIN_METHODS),
vol.Required(CONF_USERNAME): cv.string,
vol.Required(CONF_PASSWORD): cv.string,
vol.Optional(CONF_INSTALL_ID): cv.string,
vol.Optional(
CONF_TIMEOUT, default=DEFAULT_TIMEOUT
): cv.positive_int,
}
)
},
),
extra=vol.ALLOW_EXTRA,
)
async def async_request_validation(hass, config_entry, august_gateway):
"""Request a new verification code from the user."""
#
# In the future this should start a new config flow
# instead of using the legacy configurator
#
_LOGGER.error("Access token is no longer valid")
configurator = hass.components.configurator
entry_id = config_entry.entry_id
async def async_august_configuration_validation_callback(data):
code = data.get(VERIFICATION_CODE_KEY)
result = await august_gateway.authenticator.async_validate_verification_code(
code
)
if result == ValidationResult.INVALID_VERIFICATION_CODE:
configurator.async_notify_errors(
hass.data[DOMAIN][entry_id][TWO_FA_REVALIDATE],
"Invalid verification code, please make sure you are using the latest code and try again.",
)
elif result == ValidationResult.VALIDATED:
return await async_setup_august(hass, config_entry, august_gateway)
return False
if TWO_FA_REVALIDATE not in hass.data[DOMAIN][entry_id]:
await august_gateway.authenticator.async_send_verification_code()
entry_data = config_entry.data
login_method = entry_data.get(CONF_LOGIN_METHOD)
username = entry_data.get(CONF_USERNAME)
hass.data[DOMAIN][entry_id][TWO_FA_REVALIDATE] = configurator.async_request_config(
f"{DEFAULT_NAME} ({username})",
async_august_configuration_validation_callback,
description=(
"August must be re-verified. "
f"Please check your {login_method} ({username}) "
"and enter the verification code below"
),
submit_caption="Verify",
fields=[
{"id": VERIFICATION_CODE_KEY, "name": "Verification code", "type": "string"}
],
)
return
async def async_setup_august(hass, config_entry, august_gateway):
"""Set up the August component."""
entry_id = config_entry.entry_id
hass.data[DOMAIN].setdefault(entry_id, {})
try:
await august_gateway.async_authenticate()
except RequireValidation:
await async_request_validation(hass, config_entry, august_gateway)
raise
# We still use the configurator to get a new 2fa code
# when needed since config_flow doesn't have a way
# to re-request if it expires
if TWO_FA_REVALIDATE in hass.data[DOMAIN][entry_id]:
hass.components.configurator.async_request_done(
hass.data[DOMAIN][entry_id].pop(TWO_FA_REVALIDATE)
)
hass.data[DOMAIN][entry_id][DATA_AUGUST] = AugustData(hass, august_gateway)
await hass.data[DOMAIN][entry_id][DATA_AUGUST].async_setup()
for platform in PLATFORMS:
hass.async_create_task(
hass.config_entries.async_forward_entry_setup(config_entry, platform)
)
return True
async def async_setup(hass: HomeAssistant, config: dict): async def async_setup(hass: HomeAssistant, config: dict):
"""Set up the August component from YAML.""" """Set up the August component from YAML."""
conf = config.get(DOMAIN)
hass.data.setdefault(DOMAIN, {}) hass.data.setdefault(DOMAIN, {})
if not conf:
return True
hass.async_create_task(
hass.config_entries.flow.async_init(
DOMAIN,
context={"source": SOURCE_IMPORT},
data={
CONF_LOGIN_METHOD: conf.get(CONF_LOGIN_METHOD),
CONF_USERNAME: conf.get(CONF_USERNAME),
CONF_PASSWORD: conf.get(CONF_PASSWORD),
CONF_INSTALL_ID: conf.get(CONF_INSTALL_ID),
CONF_ACCESS_TOKEN_CACHE_FILE: DEFAULT_AUGUST_CONFIG_FILE,
},
)
)
return True return True
@ -184,11 +40,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry):
return False return False
raise ConfigEntryNotReady from err raise ConfigEntryNotReady from err
except InvalidAuth: except (RequireValidation, InvalidAuth):
_async_start_reauth(hass, entry) _async_start_reauth(hass, entry)
return False return False
except RequireValidation:
return False
except (CannotConnect, asyncio.TimeoutError) as err: except (CannotConnect, asyncio.TimeoutError) as err:
raise ConfigEntryNotReady from err raise ConfigEntryNotReady from err
@ -221,6 +75,31 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry):
return unload_ok return unload_ok
async def async_setup_august(hass, config_entry, august_gateway):
"""Set up the August component."""
if CONF_PASSWORD in config_entry.data:
# We no longer need to store passwords since we do not
# support YAML anymore
config_data = config_entry.data.copy()
del config_data[CONF_PASSWORD]
hass.config_entries.async_update_entry(config_entry, data=config_data)
await august_gateway.async_authenticate()
data = hass.data[DOMAIN][config_entry.entry_id] = {
DATA_AUGUST: AugustData(hass, august_gateway)
}
await data[DATA_AUGUST].async_setup()
for platform in PLATFORMS:
hass.async_create_task(
hass.config_entries.async_forward_entry_setup(config_entry, platform)
)
return True
class AugustData(AugustSubscriberMixin): class AugustData(AugustSubscriberMixin):
"""August data object.""" """August data object."""

View file

@ -5,14 +5,9 @@ from august.authenticator import ValidationResult
import voluptuous as vol import voluptuous as vol
from homeassistant import config_entries from homeassistant import config_entries
from homeassistant.const import CONF_PASSWORD, CONF_TIMEOUT, CONF_USERNAME from homeassistant.const import CONF_PASSWORD, CONF_USERNAME
from .const import ( from .const import CONF_LOGIN_METHOD, LOGIN_METHODS, VERIFICATION_CODE_KEY
CONF_LOGIN_METHOD,
DEFAULT_TIMEOUT,
LOGIN_METHODS,
VERIFICATION_CODE_KEY,
)
from .const import DOMAIN # pylint:disable=unused-import from .const import DOMAIN # pylint:disable=unused-import
from .exceptions import CannotConnect, InvalidAuth, RequireValidation from .exceptions import CannotConnect, InvalidAuth, RequireValidation
from .gateway import AugustGateway from .gateway import AugustGateway
@ -68,61 +63,48 @@ class AugustConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
def __init__(self): def __init__(self):
"""Store an AugustGateway().""" """Store an AugustGateway()."""
self._august_gateway = None self._august_gateway = None
self.user_auth_details = {} self._user_auth_details = {}
self._needs_reset = False self._needs_reset = False
self._mode = None
super().__init__() super().__init__()
async def async_step_user(self, user_input=None): async def async_step_user(self, user_input=None):
"""Handle the initial step.""" """Handle the initial step."""
if self._august_gateway is None: self._august_gateway = AugustGateway(self.hass)
self._august_gateway = AugustGateway(self.hass) return await self.async_step_user_validate()
async def async_step_user_validate(self, user_input=None):
"""Handle authentication."""
errors = {} errors = {}
if user_input is not None: if user_input is not None:
combined_inputs = {**self.user_auth_details, **user_input} result = await self._async_auth_or_validate(user_input, errors)
await self._august_gateway.async_setup(combined_inputs) if result is not None:
if self._needs_reset: return result
self._needs_reset = False
await self._august_gateway.async_reset_authentication()
try:
info = await async_validate_input(
combined_inputs,
self._august_gateway,
)
except CannotConnect:
errors["base"] = "cannot_connect"
except InvalidAuth:
errors["base"] = "invalid_auth"
except RequireValidation:
self.user_auth_details.update(user_input)
return await self.async_step_validation()
except Exception: # pylint: disable=broad-except
_LOGGER.exception("Unexpected exception")
errors["base"] = "unknown"
if not errors:
self.user_auth_details.update(user_input)
existing_entry = await self.async_set_unique_id(
combined_inputs[CONF_USERNAME]
)
if existing_entry:
self.hass.config_entries.async_update_entry(
existing_entry, data=info["data"]
)
await self.hass.config_entries.async_reload(existing_entry.entry_id)
return self.async_abort(reason="reauth_successful")
return self.async_create_entry(title=info["title"], data=info["data"])
return self.async_show_form( return self.async_show_form(
step_id="user", data_schema=self._async_build_schema(), errors=errors step_id="user_validate",
data_schema=vol.Schema(
{
vol.Required(
CONF_LOGIN_METHOD,
default=self._user_auth_details.get(CONF_LOGIN_METHOD, "phone"),
): vol.In(LOGIN_METHODS),
vol.Required(
CONF_USERNAME,
default=self._user_auth_details.get(CONF_USERNAME),
): str,
vol.Required(CONF_PASSWORD): str,
}
),
errors=errors,
) )
async def async_step_validation(self, user_input=None): async def async_step_validation(self, user_input=None):
"""Handle validation (2fa) step.""" """Handle validation (2fa) step."""
if user_input: if user_input:
return await self.async_step_user({**self.user_auth_details, **user_input}) if self._mode == "reauth":
return await self.async_step_reauth_validate(user_input)
return await self.async_step_user_validate(user_input)
return self.async_show_form( return self.async_show_form(
step_id="validation", step_id="validation",
@ -130,34 +112,70 @@ class AugustConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
{vol.Required(VERIFICATION_CODE_KEY): vol.All(str, vol.Strip)} {vol.Required(VERIFICATION_CODE_KEY): vol.All(str, vol.Strip)}
), ),
description_placeholders={ description_placeholders={
CONF_USERNAME: self.user_auth_details.get(CONF_USERNAME), CONF_USERNAME: self._user_auth_details[CONF_USERNAME],
CONF_LOGIN_METHOD: self.user_auth_details.get(CONF_LOGIN_METHOD), CONF_LOGIN_METHOD: self._user_auth_details[CONF_LOGIN_METHOD],
}, },
) )
async def async_step_import(self, user_input):
"""Handle import."""
await self.async_set_unique_id(user_input[CONF_USERNAME])
self._abort_if_unique_id_configured()
return await self.async_step_user(user_input)
async def async_step_reauth(self, data): async def async_step_reauth(self, data):
"""Handle configuration by re-auth.""" """Handle configuration by re-auth."""
self.user_auth_details = dict(data) self._user_auth_details = dict(data)
self._mode = "reauth"
self._needs_reset = True self._needs_reset = True
return await self.async_step_user() self._august_gateway = AugustGateway(self.hass)
return await self.async_step_reauth_validate()
def _async_build_schema(self): async def async_step_reauth_validate(self, user_input=None):
"""Generate the config flow schema.""" """Handle reauth and validation."""
base_schema = { errors = {}
vol.Required(CONF_LOGIN_METHOD, default="phone"): vol.In(LOGIN_METHODS), if user_input is not None:
vol.Required(CONF_USERNAME): str, result = await self._async_auth_or_validate(user_input, errors)
vol.Required(CONF_PASSWORD): str, if result is not None:
vol.Optional(CONF_TIMEOUT, default=DEFAULT_TIMEOUT): vol.Coerce(int), return result
}
for key in self.user_auth_details: return self.async_show_form(
if key == CONF_PASSWORD or key not in base_schema: step_id="reauth_validate",
continue data_schema=vol.Schema(
del base_schema[key] {
return vol.Schema(base_schema) vol.Required(CONF_PASSWORD): str,
}
),
errors=errors,
description_placeholders={
CONF_USERNAME: self._user_auth_details[CONF_USERNAME],
},
)
async def _async_auth_or_validate(self, user_input, errors):
self._user_auth_details.update(user_input)
await self._august_gateway.async_setup(self._user_auth_details)
if self._needs_reset:
self._needs_reset = False
await self._august_gateway.async_reset_authentication()
try:
info = await async_validate_input(
self._user_auth_details,
self._august_gateway,
)
except CannotConnect:
errors["base"] = "cannot_connect"
except InvalidAuth:
errors["base"] = "invalid_auth"
except RequireValidation:
return await self.async_step_validation()
except Exception: # pylint: disable=broad-except
_LOGGER.exception("Unexpected exception")
errors["base"] = "unknown"
if errors:
return None
existing_entry = await self.async_set_unique_id(
self._user_auth_details[CONF_USERNAME]
)
if not existing_entry:
return self.async_create_entry(title=info["title"], data=info["data"])
self.hass.config_entries.async_update_entry(existing_entry, data=info["data"])
await self.hass.config_entries.async_reload(existing_entry.entry_id)
return self.async_abort(reason="reauth_successful")

View file

@ -21,6 +21,7 @@ from .const import (
CONF_INSTALL_ID, CONF_INSTALL_ID,
CONF_LOGIN_METHOD, CONF_LOGIN_METHOD,
DEFAULT_AUGUST_CONFIG_FILE, DEFAULT_AUGUST_CONFIG_FILE,
DEFAULT_TIMEOUT,
VERIFICATION_CODE_KEY, VERIFICATION_CODE_KEY,
) )
from .exceptions import CannotConnect, InvalidAuth, RequireValidation from .exceptions import CannotConnect, InvalidAuth, RequireValidation
@ -52,9 +53,7 @@ class AugustGateway:
return { return {
CONF_LOGIN_METHOD: self._config[CONF_LOGIN_METHOD], CONF_LOGIN_METHOD: self._config[CONF_LOGIN_METHOD],
CONF_USERNAME: self._config[CONF_USERNAME], CONF_USERNAME: self._config[CONF_USERNAME],
CONF_PASSWORD: self._config[CONF_PASSWORD],
CONF_INSTALL_ID: self._config.get(CONF_INSTALL_ID), CONF_INSTALL_ID: self._config.get(CONF_INSTALL_ID),
CONF_TIMEOUT: self._config.get(CONF_TIMEOUT),
CONF_ACCESS_TOKEN_CACHE_FILE: self._access_token_cache_file, CONF_ACCESS_TOKEN_CACHE_FILE: self._access_token_cache_file,
} }
@ -70,14 +69,15 @@ class AugustGateway:
self._config = conf self._config = conf
self.api = ApiAsync( self.api = ApiAsync(
self._aiohttp_session, timeout=self._config.get(CONF_TIMEOUT) self._aiohttp_session,
timeout=self._config.get(CONF_TIMEOUT, DEFAULT_TIMEOUT),
) )
self.authenticator = AuthenticatorAsync( self.authenticator = AuthenticatorAsync(
self.api, self.api,
self._config[CONF_LOGIN_METHOD], self._config[CONF_LOGIN_METHOD],
self._config[CONF_USERNAME], self._config[CONF_USERNAME],
self._config[CONF_PASSWORD], self._config.get(CONF_PASSWORD, ""),
install_id=self._config.get(CONF_INSTALL_ID), install_id=self._config.get(CONF_INSTALL_ID),
access_token_cache_file=self._hass.config.path( access_token_cache_file=self._hass.config.path(
self._access_token_cache_file self._access_token_cache_file
@ -128,14 +128,15 @@ class AugustGateway:
async def async_refresh_access_token_if_needed(self): async def async_refresh_access_token_if_needed(self):
"""Refresh the august access token if needed.""" """Refresh the august access token if needed."""
if self.authenticator.should_refresh(): if not self.authenticator.should_refresh():
async with self._token_refresh_lock: return
refreshed_authentication = ( async with self._token_refresh_lock:
await self.authenticator.async_refresh_access_token(force=False) refreshed_authentication = (
) await self.authenticator.async_refresh_access_token(force=False)
_LOGGER.info( )
"Refreshed august access token. The old token expired at %s, and the new token expires at %s", _LOGGER.info(
self.authentication.access_token_expires, "Refreshed august access token. The old token expired at %s, and the new token expires at %s",
refreshed_authentication.access_token_expires, self.authentication.access_token_expires,
) refreshed_authentication.access_token_expires,
self.authentication = refreshed_authentication )
self.authentication = refreshed_authentication

View file

@ -3,7 +3,6 @@
"name": "August", "name": "August",
"documentation": "https://www.home-assistant.io/integrations/august", "documentation": "https://www.home-assistant.io/integrations/august",
"requirements": ["py-august==0.25.2"], "requirements": ["py-august==0.25.2"],
"dependencies": ["configurator"],
"codeowners": ["@bdraco"], "codeowners": ["@bdraco"],
"dhcp": [ "dhcp": [
{"hostname":"connect","macaddress":"D86162*"}, {"hostname":"connect","macaddress":"D86162*"},

View file

@ -17,15 +17,21 @@
}, },
"description": "Please check your {login_method} ({username}) and enter the verification code below" "description": "Please check your {login_method} ({username}) and enter the verification code below"
}, },
"user": { "user_validate": {
"description": "If the Login Method is 'email', Username is the email address. If the Login Method is 'phone', Username is the phone number in the format '+NNNNNNNNN'.", "description": "If the Login Method is 'email', Username is the email address. If the Login Method is 'phone', Username is the phone number in the format '+NNNNNNNNN'.",
"data": { "data": {
"timeout": "Timeout (seconds)",
"password": "[%key:common::config_flow::data::password%]", "password": "[%key:common::config_flow::data::password%]",
"username": "[%key:common::config_flow::data::username%]", "username": "[%key:common::config_flow::data::username%]",
"login_method": "Login Method" "login_method": "Login Method"
}, },
"title": "Setup an August account" "title": "Setup an August account"
},
"reauth_validate": {
"description": "Enter the password for {username}.",
"data": {
"password": "[%key:common::config_flow::data::password%]"
},
"title": "Reauthenticate an August account"
} }
} }
} }

View file

@ -10,11 +10,17 @@
"unknown": "Unexpected error" "unknown": "Unexpected error"
}, },
"step": { "step": {
"user": { "reauth_validate": {
"data": {
"password": "Password"
},
"description": "Enter the password for {username}.",
"title": "Reauthenticate an August account"
},
"user_validate": {
"data": { "data": {
"login_method": "Login Method", "login_method": "Login Method",
"password": "Password", "password": "Password",
"timeout": "Timeout (seconds)",
"username": "Username" "username": "Username"
}, },
"description": "If the Login Method is 'email', Username is the email address. If the Login Method is 'phone', Username is the phone number in the format '+NNNNNNNNN'.", "description": "If the Login Method is 'email', Username is the email address. If the Login Method is 'phone', Username is the phone number in the format '+NNNNNNNNN'.",

View file

@ -22,15 +22,10 @@ from august.authenticator import AuthenticationState
from august.doorbell import Doorbell, DoorbellDetail from august.doorbell import Doorbell, DoorbellDetail
from august.lock import Lock, LockDetail from august.lock import Lock, LockDetail
from homeassistant.components.august import ( from homeassistant.components.august.const import CONF_LOGIN_METHOD, DOMAIN
CONF_LOGIN_METHOD, from homeassistant.const import CONF_PASSWORD, CONF_USERNAME
CONF_PASSWORD,
CONF_USERNAME,
DOMAIN,
)
from homeassistant.setup import async_setup_component
from tests.common import load_fixture from tests.common import MockConfigEntry, load_fixture
def _mock_get_config(): def _mock_get_config():
@ -61,7 +56,13 @@ async def _mock_setup_august(hass, api_instance, authenticate_mock, api_mock):
) )
) )
api_mock.return_value = api_instance api_mock.return_value = api_instance
assert await async_setup_component(hass, DOMAIN, _mock_get_config()) entry = MockConfigEntry(
domain=DOMAIN,
data=_mock_get_config()[DOMAIN],
options={},
)
entry.add_to_hass(hass)
assert await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done() await hass.async_block_till_done()
return True return True

View file

@ -54,9 +54,7 @@ async def test_form(hass):
assert result2["data"] == { assert result2["data"] == {
CONF_LOGIN_METHOD: "email", CONF_LOGIN_METHOD: "email",
CONF_USERNAME: "my@email.tld", CONF_USERNAME: "my@email.tld",
CONF_PASSWORD: "test-password",
CONF_INSTALL_ID: None, CONF_INSTALL_ID: None,
CONF_TIMEOUT: 10,
CONF_ACCESS_TOKEN_CACHE_FILE: ".my@email.tld.august.conf", CONF_ACCESS_TOKEN_CACHE_FILE: ".my@email.tld.august.conf",
} }
assert len(mock_setup.mock_calls) == 1 assert len(mock_setup.mock_calls) == 1
@ -215,9 +213,7 @@ async def test_form_needs_validate(hass):
assert result4["data"] == { assert result4["data"] == {
CONF_LOGIN_METHOD: "email", CONF_LOGIN_METHOD: "email",
CONF_USERNAME: "my@email.tld", CONF_USERNAME: "my@email.tld",
CONF_PASSWORD: "test-password",
CONF_INSTALL_ID: None, CONF_INSTALL_ID: None,
CONF_TIMEOUT: 10,
CONF_ACCESS_TOKEN_CACHE_FILE: ".my@email.tld.august.conf", CONF_ACCESS_TOKEN_CACHE_FILE: ".my@email.tld.august.conf",
} }
assert len(mock_setup.mock_calls) == 1 assert len(mock_setup.mock_calls) == 1
@ -268,3 +264,75 @@ async def test_form_reauth(hass):
assert result2["reason"] == "reauth_successful" assert result2["reason"] == "reauth_successful"
assert len(mock_setup.mock_calls) == 1 assert len(mock_setup.mock_calls) == 1
assert len(mock_setup_entry.mock_calls) == 1 assert len(mock_setup_entry.mock_calls) == 1
async def test_form_reauth_with_2fa(hass):
"""Test reauthenticate with 2fa."""
entry = MockConfigEntry(
domain=DOMAIN,
data={
CONF_LOGIN_METHOD: "email",
CONF_USERNAME: "my@email.tld",
CONF_PASSWORD: "test-password",
CONF_INSTALL_ID: None,
CONF_TIMEOUT: 10,
CONF_ACCESS_TOKEN_CACHE_FILE: ".my@email.tld.august.conf",
},
unique_id="my@email.tld",
)
entry.add_to_hass(hass)
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": "reauth"}, data=entry.data
)
assert result["type"] == "form"
assert result["errors"] == {}
with patch(
"homeassistant.components.august.config_flow.AugustGateway.async_authenticate",
side_effect=RequireValidation,
), patch(
"homeassistant.components.august.gateway.AuthenticatorAsync.async_send_verification_code",
return_value=True,
) as mock_send_verification_code:
result2 = await hass.config_entries.flow.async_configure(
result["flow_id"],
{
CONF_PASSWORD: "new-test-password",
},
)
await hass.async_block_till_done()
assert len(mock_send_verification_code.mock_calls) == 1
assert result2["type"] == "form"
assert result2["errors"] is None
assert result2["step_id"] == "validation"
# Try with the CORRECT verification code and we setup
with patch(
"homeassistant.components.august.config_flow.AugustGateway.async_authenticate",
return_value=True,
), patch(
"homeassistant.components.august.gateway.AuthenticatorAsync.async_validate_verification_code",
return_value=ValidationResult.VALIDATED,
) as mock_validate_verification_code, patch(
"homeassistant.components.august.gateway.AuthenticatorAsync.async_send_verification_code",
return_value=True,
) as mock_send_verification_code, patch(
"homeassistant.components.august.async_setup", return_value=True
) as mock_setup, patch(
"homeassistant.components.august.async_setup_entry", return_value=True
) as mock_setup_entry:
result3 = await hass.config_entries.flow.async_configure(
result2["flow_id"],
{VERIFICATION_CODE_KEY: "correct"},
)
await hass.async_block_till_done()
assert len(mock_validate_verification_code.mock_calls) == 1
assert len(mock_send_verification_code.mock_calls) == 0
assert result3["type"] == "abort"
assert result3["reason"] == "reauth_successful"
assert len(mock_setup.mock_calls) == 1
assert len(mock_setup_entry.mock_calls) == 1

View file

@ -7,13 +7,7 @@ from august.authenticator_common import AuthenticationState
from august.exceptions import AugustApiAIOHTTPError from august.exceptions import AugustApiAIOHTTPError
from homeassistant import setup from homeassistant import setup
from homeassistant.components.august.const import ( from homeassistant.components.august.const import DOMAIN
CONF_ACCESS_TOKEN_CACHE_FILE,
CONF_INSTALL_ID,
CONF_LOGIN_METHOD,
DEFAULT_AUGUST_CONFIG_FILE,
DOMAIN,
)
from homeassistant.components.lock import DOMAIN as LOCK_DOMAIN from homeassistant.components.lock import DOMAIN as LOCK_DOMAIN
from homeassistant.config_entries import ( from homeassistant.config_entries import (
ENTRY_STATE_SETUP_ERROR, ENTRY_STATE_SETUP_ERROR,
@ -21,16 +15,12 @@ from homeassistant.config_entries import (
) )
from homeassistant.const import ( from homeassistant.const import (
ATTR_ENTITY_ID, ATTR_ENTITY_ID,
CONF_PASSWORD,
CONF_TIMEOUT,
CONF_USERNAME,
SERVICE_LOCK, SERVICE_LOCK,
SERVICE_UNLOCK, SERVICE_UNLOCK,
STATE_LOCKED, STATE_LOCKED,
STATE_ON, STATE_ON,
) )
from homeassistant.exceptions import HomeAssistantError from homeassistant.exceptions import HomeAssistantError
from homeassistant.setup import async_setup_component
from tests.common import MockConfigEntry from tests.common import MockConfigEntry
from tests.components.august.mocks import ( from tests.components.august.mocks import (
@ -149,35 +139,6 @@ async def test_lock_has_doorsense(hass):
assert binary_sensor_missing_doorsense_id_name_open is None assert binary_sensor_missing_doorsense_id_name_open is None
async def test_set_up_from_yaml(hass):
"""Test to make sure config is imported from yaml."""
await setup.async_setup_component(hass, "persistent_notification", {})
with patch(
"homeassistant.components.august.async_setup_august",
return_value=True,
) as mock_setup_august, patch(
"homeassistant.components.august.config_flow.AugustGateway.async_authenticate",
return_value=True,
):
assert await async_setup_component(hass, DOMAIN, _mock_get_config())
await hass.async_block_till_done()
assert len(mock_setup_august.mock_calls) == 1
call = mock_setup_august.call_args
args, _ = call
imported_config_entry = args[1]
# The import must use DEFAULT_AUGUST_CONFIG_FILE so they
# do not loose their token when config is migrated
assert imported_config_entry.data == {
CONF_ACCESS_TOKEN_CACHE_FILE: DEFAULT_AUGUST_CONFIG_FILE,
CONF_INSTALL_ID: None,
CONF_LOGIN_METHOD: "email",
CONF_PASSWORD: "mocked_password",
CONF_TIMEOUT: None,
CONF_USERNAME: "mocked_username",
}
async def test_auth_fails(hass): async def test_auth_fails(hass):
"""Config entry state is ENTRY_STATE_SETUP_ERROR when auth fails.""" """Config entry state is ENTRY_STATE_SETUP_ERROR when auth fails."""
@ -201,7 +162,7 @@ async def test_auth_fails(hass):
flows = hass.config_entries.flow.async_progress() flows = hass.config_entries.flow.async_progress()
assert flows[0]["step_id"] == "user" assert flows[0]["step_id"] == "reauth_validate"
async def test_bad_password(hass): async def test_bad_password(hass):
@ -229,7 +190,7 @@ async def test_bad_password(hass):
flows = hass.config_entries.flow.async_progress() flows = hass.config_entries.flow.async_progress()
assert flows[0]["step_id"] == "user" assert flows[0]["step_id"] == "reauth_validate"
async def test_http_failure(hass): async def test_http_failure(hass):
@ -279,7 +240,7 @@ async def test_unknown_auth_state(hass):
flows = hass.config_entries.flow.async_progress() flows = hass.config_entries.flow.async_progress()
assert flows[0]["step_id"] == "user" assert flows[0]["step_id"] == "reauth_validate"
async def test_requires_validation_state(hass): async def test_requires_validation_state(hass):
@ -305,4 +266,5 @@ async def test_requires_validation_state(hass):
assert config_entry.state == ENTRY_STATE_SETUP_ERROR assert config_entry.state == ENTRY_STATE_SETUP_ERROR
assert hass.config_entries.flow.async_progress() == [] assert len(hass.config_entries.flow.async_progress()) == 1
assert hass.config_entries.flow.async_progress()[0]["context"]["source"] == "reauth"