Skip to content

Commit

Permalink
Updated testing coverage
Browse files Browse the repository at this point in the history
  • Loading branch information
jeeftor committed Aug 16, 2024
1 parent c9a5138 commit 3537050
Show file tree
Hide file tree
Showing 5 changed files with 230 additions and 84 deletions.
100 changes: 80 additions & 20 deletions homeassistant/components/monarchmoney/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import logging
from typing import Any

from monarchmoney import LoginFailedException, MonarchMoney
from monarchmoney import LoginFailedException, MonarchMoney, RequireMFAException
from monarchmoney.monarchmoney import SESSION_FILE
import voluptuous as vol

Expand All @@ -14,40 +14,59 @@
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError

from .const import CONF_MFA_SECRET, DOMAIN, LOGGER
from .const import CONF_MFA_CODE, DOMAIN, LOGGER

_LOGGER = logging.getLogger(__name__)


STEP_USER_DATA_SCHEMA = vol.Schema(
{
vol.Required(CONF_EMAIL): str,
vol.Required(CONF_PASSWORD): str,
vol.Required(CONF_MFA_SECRET): str,
}
)

STEP_MFA_DATA_SCHEMA = vol.Schema(
{
vol.Required(CONF_MFA_CODE): str,
}
)

async def validate_input(hass: HomeAssistant, data: dict[str, Any]) -> dict[str, Any]:

async def validate_login(
hass: HomeAssistant,
data: dict[str, Any],
email: str | None = None,
password: str | None = None,
) -> dict[str, Any]:
"""Validate the user input allows us to connect.
Data has the keys from STEP_USER_DATA_SCHEMA with values provided by the user. Upon success a session will be saved
"""
mfa_secret_key = data.get(CONF_MFA_SECRET, "")
email = data[CONF_EMAIL]
password = data[CONF_PASSWORD]

# Test that we can login:
# mfa_secret_key = data.get(CONF_MFA_SECRET, "")
if not email:
email = data[CONF_EMAIL]
if not password:
password = data[CONF_PASSWORD]
monarch_client = MonarchMoney()
try:
await monarch_client.login(
email=email,
password=password,
save_session=False,
use_saved_session=False,
mfa_secret_key=mfa_secret_key,
)
except LoginFailedException as exc:
raise InvalidAuth from exc
if CONF_MFA_CODE in data:
mfa_code = data[CONF_MFA_CODE]
try:
await monarch_client.multi_factor_authenticate(email, password, mfa_code)
except LoginFailedException as err:
raise InvalidAuth from err
else:
try:
await monarch_client.login(
email=email,
password=password,
save_session=False,
use_saved_session=False,
)
except RequireMFAException as err:
raise RequireMFAException from err
except LoginFailedException as err:
raise InvalidAuth from err

# monarch_client.token
LOGGER.debug(f"Connection successful - saving session to file {SESSION_FILE}")
Expand All @@ -61,14 +80,31 @@ class MonarchMoneyConfigFlow(ConfigFlow, domain=DOMAIN):

VERSION = 1

def __init__(self):
"""Initialize config flow."""
self.email: str | None = None
self.password: str | None = None

async def async_step_user(
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
"""Handle the initial step."""
errors: dict[str, str] = {}

if user_input is not None:
try:
info = await validate_input(self.hass, user_input)
info = await validate_login(
self.hass, user_input, email=self.email, password=self.password
)
except RequireMFAException:
self.email = user_input[CONF_EMAIL]
self.password = user_input[CONF_PASSWORD]

return self.async_show_form(
step_id="user",
data_schema=STEP_MFA_DATA_SCHEMA,
errors={"base": "mfa_required"},
)
except InvalidAuth:
errors["base"] = "invalid_auth"
except Exception:
Expand All @@ -84,5 +120,29 @@ async def async_step_user(
)


#
# async def old_async_step_user(
# self, user_input: dict[str, Any] | None = None
# ) -> ConfigFlowResult:
# """Handle the initial step."""
# errors: dict[str, str] = {}
# if user_input is not None:
# try:
# info = await validate_input(self.hass, user_input)
# except InvalidAuth:
# errors["base"] = "invalid_auth"
# except Exception:
# _LOGGER.exception("Unexpected exception")
# errors["base"] = "unknown"
# else:
# return self.async_create_entry(
# title=info["title"], data={CONF_TOKEN: info[CONF_TOKEN]}
# )
#
# return self.async_show_form(
# step_id="user", data_schema=STEP_USER_DATA_SCHEMA, errors=errors
# )


class InvalidAuth(HomeAssistantError):
"""Error to indicate there is invalid auth."""
1 change: 1 addition & 0 deletions homeassistant/components/monarchmoney/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@
LOGGER = logging.getLogger(__package__)

CONF_MFA_SECRET = "mfa_secret"
CONF_MFA_CODE = "mfa_code"
5 changes: 4 additions & 1 deletion homeassistant/components/monarchmoney/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@
"config": {
"step": {
"user": {
"description": "Enter your Monarch Money email and password, if required you will also be prompted for your MFA code.",
"data": {
"mfa_secret": "Add your MFA Secret. See docs for help.",
"mfa_code": "Enter your MFA code",
"email": "[%key:common::config_flow::data::email%]",
"password": "[%key:common::config_flow::data::password%]"
}
Expand All @@ -12,7 +14,8 @@
"error": {
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
"invalid_auth": "[%key:common::config_flow::error::invalid_auth%]",
"unknown": "[%key:common::config_flow::error::unknown%]"
"unknown": "[%key:common::config_flow::error::unknown%]",
"mfa_required": "Multi-factor authentication required."
},
"abort": {
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]"
Expand Down
1 change: 1 addition & 0 deletions tests/components/monarchmoney/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ def mock_config_api() -> Generator[AsyncMock]:
instance = mock_class.return_value
type(instance).token = PropertyMock(return_value="mocked_token")
instance.login = AsyncMock(return_value=None)
instance.multi_factor_authenticate = AsyncMock(return_value=None)
instance.get_subscription_details = AsyncMock(
return_value={
"subscription": {
Expand Down
Loading

0 comments on commit 3537050

Please sign in to comment.