From ff5ffd2a645676e18ea964cfa2cd38011a9e2793 Mon Sep 17 00:00:00 2001 From: Jeff Stein Date: Thu, 15 Aug 2024 12:58:52 -0600 Subject: [PATCH 01/67] Initial commit --- CODEOWNERS | 2 + .../components/monarchmoney/__init__.py | 44 ++ .../components/monarchmoney/config_flow.py | 88 ++++ .../components/monarchmoney/const.py | 9 + .../components/monarchmoney/coordinator.py | 46 ++ .../components/monarchmoney/entity.py | 56 +++ .../components/monarchmoney/manifest.json | 13 + .../components/monarchmoney/sensor.py | 148 ++++++ .../components/monarchmoney/strings.json | 32 ++ homeassistant/generated/config_flows.py | 1 + homeassistant/generated/integrations.json | 12 + requirements_all.txt | 3 + requirements_test_all.txt | 3 + tests/components/monarchmoney/__init__.py | 1 + tests/components/monarchmoney/conftest.py | 47 ++ .../monarchmoney/fixtures/get_accounts.json | 429 ++++++++++++++++++ .../monarchmoney/test_config_flow.py | 170 +++++++ tests/components/monarchmoney/test_sensor.py | 1 + 18 files changed, 1105 insertions(+) create mode 100644 homeassistant/components/monarchmoney/__init__.py create mode 100644 homeassistant/components/monarchmoney/config_flow.py create mode 100644 homeassistant/components/monarchmoney/const.py create mode 100644 homeassistant/components/monarchmoney/coordinator.py create mode 100644 homeassistant/components/monarchmoney/entity.py create mode 100644 homeassistant/components/monarchmoney/manifest.json create mode 100644 homeassistant/components/monarchmoney/sensor.py create mode 100644 homeassistant/components/monarchmoney/strings.json create mode 100644 tests/components/monarchmoney/__init__.py create mode 100644 tests/components/monarchmoney/conftest.py create mode 100644 tests/components/monarchmoney/fixtures/get_accounts.json create mode 100644 tests/components/monarchmoney/test_config_flow.py create mode 100644 tests/components/monarchmoney/test_sensor.py diff --git a/CODEOWNERS b/CODEOWNERS index bd4494b8249ea6..6e1227a8fb675a 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -922,6 +922,8 @@ build.json @home-assistant/supervisor /tests/components/modern_forms/ @wonderslug /homeassistant/components/moehlenhoff_alpha2/ @j-a-n /tests/components/moehlenhoff_alpha2/ @j-a-n +/homeassistant/components/monarchmoney/ @jeeftor +/tests/components/monarchmoney/ @jeeftor /homeassistant/components/monoprice/ @etsinko @OnFreund /tests/components/monoprice/ @etsinko @OnFreund /homeassistant/components/monzo/ @jakemartin-icl diff --git a/homeassistant/components/monarchmoney/__init__.py b/homeassistant/components/monarchmoney/__init__.py new file mode 100644 index 00000000000000..372e0316d9b863 --- /dev/null +++ b/homeassistant/components/monarchmoney/__init__.py @@ -0,0 +1,44 @@ +"""The Monarch Money integration.""" + +from __future__ import annotations + +from aiohttp import ClientResponseError +from gql.transport.exceptions import TransportServerError +from monarchmoney import MonarchMoney +from monarchmoney.monarchmoney import LoginFailedException + +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import CONF_TOKEN, Platform +from homeassistant.core import HomeAssistant +from homeassistant.exceptions import ConfigEntryError + +from .coordinator import MonarchMoneyDataUpdateCoordinator + +MonarchMoneyConfigEntry = ConfigEntry[MonarchMoneyDataUpdateCoordinator] # noqa: F821 + +PLATFORMS: list[Platform] = [Platform.SENSOR] + + +async def async_setup_entry( + hass: HomeAssistant, entry: MonarchMoneyConfigEntry +) -> bool: + """Set up Monarch Money from a config entry.""" + monarch_client = MonarchMoney(token=entry.data.get(CONF_TOKEN)) + + try: + await monarch_client.get_subscription_details() + except (TransportServerError, LoginFailedException, ClientResponseError) as err: + raise ConfigEntryError("Authentication failed") from err + + mm_coordinator = MonarchMoneyDataUpdateCoordinator(hass, monarch_client) + await mm_coordinator.async_config_entry_first_refresh() + entry.runtime_data = mm_coordinator + await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) + return True + + +async def async_unload_entry( + hass: HomeAssistant, entry: MonarchMoneyConfigEntry +) -> bool: + """Unload a config entry.""" + return await hass.config_entries.async_unload_platforms(entry, PLATFORMS) diff --git a/homeassistant/components/monarchmoney/config_flow.py b/homeassistant/components/monarchmoney/config_flow.py new file mode 100644 index 00000000000000..5c25eeb761dadd --- /dev/null +++ b/homeassistant/components/monarchmoney/config_flow.py @@ -0,0 +1,88 @@ +"""Config flow for Monarch Money integration.""" + +from __future__ import annotations + +import logging +from typing import Any + +from monarchmoney import LoginFailedException, MonarchMoney +from monarchmoney.monarchmoney import SESSION_FILE +import voluptuous as vol + +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult +from homeassistant.const import CONF_EMAIL, CONF_PASSWORD, CONF_TOKEN +from homeassistant.core import HomeAssistant +from homeassistant.exceptions import HomeAssistantError + +from .const import CONF_MFA_SECRET, 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, + } +) + + +async def validate_input(hass: HomeAssistant, data: dict[str, Any]) -> 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: + 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 + + # monarch_client.token + LOGGER.debug(f"Connection successful - saving session to file {SESSION_FILE}") + + # Return info that you want to store in the config entry. + return {"title": "Monarch Money", CONF_TOKEN: monarch_client.token} + + +class MonarchMoneyConfigFlow(ConfigFlow, domain=DOMAIN): + """Handle a config flow for Monarch Money.""" + + VERSION = 1 + + 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) + 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.""" diff --git a/homeassistant/components/monarchmoney/const.py b/homeassistant/components/monarchmoney/const.py new file mode 100644 index 00000000000000..be408219d7641e --- /dev/null +++ b/homeassistant/components/monarchmoney/const.py @@ -0,0 +1,9 @@ +"""Constants for the Monarch Money integration.""" + +import logging + +DOMAIN = "monarchmoney" + +LOGGER = logging.getLogger(__package__) + +CONF_MFA_SECRET = "mfa_secret" diff --git a/homeassistant/components/monarchmoney/coordinator.py b/homeassistant/components/monarchmoney/coordinator.py new file mode 100644 index 00000000000000..602025b9366505 --- /dev/null +++ b/homeassistant/components/monarchmoney/coordinator.py @@ -0,0 +1,46 @@ +"""Data coordinator for monarch money.""" + +from datetime import timedelta +from typing import Any + +from monarchmoney import MonarchMoney + +from homeassistant.config_entries import ConfigEntry +from homeassistant.helpers.update_coordinator import DataUpdateCoordinator + +from .const import LOGGER + +AccountData = dict[str, Any] + + +class MonarchMoneyDataUpdateCoordinator(DataUpdateCoordinator[AccountData]): + """Data update coordinator for Monarch Money.""" + + config_entry: ConfigEntry + + def __init__(self, hass, client): + """Initialize the coordinator.""" + super().__init__( + hass=hass, + logger=LOGGER, + name="monarchmoney", + update_interval=timedelta(hours=4), + ) + self.client: MonarchMoney = client + + async def _async_update_data(self) -> Any: + """Fetch data for all accounts.""" + + return await self.client.get_accounts() + + @property + def accounts(self) -> Any: + """Return accounts.""" + return self.data["accounts"] + + def get_account_for_id(self, account_id: str) -> Any | None: + """Get account for id.""" + for account in self.data["accounts"]: + if account["id"] == account_id: + return account + return None diff --git a/homeassistant/components/monarchmoney/entity.py b/homeassistant/components/monarchmoney/entity.py new file mode 100644 index 00000000000000..f227f18cfcd10c --- /dev/null +++ b/homeassistant/components/monarchmoney/entity.py @@ -0,0 +1,56 @@ +"""Monarch money entity definition.""" + +from typing import Any + +from homeassistant.helpers.device_registry import DeviceEntryType, DeviceInfo +from homeassistant.helpers.entity import EntityDescription +from homeassistant.helpers.update_coordinator import CoordinatorEntity + +from .const import DOMAIN +from .coordinator import MonarchMoneyDataUpdateCoordinator + + +class MonarchMoneyEntity(CoordinatorEntity[MonarchMoneyDataUpdateCoordinator]): + """Define a generic class for Entities.""" + + _attr_has_entity_name = True + + def __init__( + self, + coordinator: MonarchMoneyDataUpdateCoordinator, + description: EntityDescription, + account: Any, + ) -> None: + """Initialize the Monarch Money Entity.""" + super().__init__(coordinator) + + self.entity_description = description + self._account_id = account["id"] + + # Parse out some fields + institution = account["institution"]["name"] + provider = account["credential"]["dataProvider"] + + self._attr_attribution = f"Data provided by Monarch Money API via {provider}" + + configuration_url = account["institution"]["url"] + if not configuration_url.startswith(("http://", "https://")): + configuration_url = f"http://{configuration_url}" + + self._attr_unique_id = ( + f"{institution}_{account["displayName"]}_{description.translation_key}" + ) + + self._attr_device_info = DeviceInfo( + identifiers={(DOMAIN, account["id"])}, + name=f"{institution} {account["displayName"]}", + entry_type=DeviceEntryType.SERVICE, + manufacturer=provider, + model=institution, + configuration_url=configuration_url, + ) + + @property + def account_data(self) -> Any: + """Return the account data.""" + return self.coordinator.get_account_for_id(self._account_id) diff --git a/homeassistant/components/monarchmoney/manifest.json b/homeassistant/components/monarchmoney/manifest.json new file mode 100644 index 00000000000000..a28ff06c02b02b --- /dev/null +++ b/homeassistant/components/monarchmoney/manifest.json @@ -0,0 +1,13 @@ +{ + "domain": "monarchmoney", + "name": "Monarch Money", + "codeowners": ["@jeeftor"], + "config_flow": true, + "dependencies": [], + "documentation": "https://www.home-assistant.io/integrations/monarchmoney", + "homekit": {}, + "iot_class": "cloud_polling", + "requirements": ["monarchmoney==0.1.13"], + "ssdp": [], + "zeroconf": [] +} diff --git a/homeassistant/components/monarchmoney/sensor.py b/homeassistant/components/monarchmoney/sensor.py new file mode 100644 index 00000000000000..6793674556bf16 --- /dev/null +++ b/homeassistant/components/monarchmoney/sensor.py @@ -0,0 +1,148 @@ +"""Sensor config - monarch money.""" + +from collections.abc import Callable +from dataclasses import dataclass +from datetime import datetime +from typing import Any + +from homeassistant.components.sensor import ( + SensorDeviceClass, + SensorEntity, + SensorEntityDescription, + SensorStateClass, +) +from homeassistant.const import EntityCategory +from homeassistant.core import HomeAssistant +from homeassistant.helpers.entity_platform import AddEntitiesCallback +from homeassistant.helpers.typing import StateType + +from . import MonarchMoneyConfigEntry +from .const import LOGGER +from .entity import MonarchMoneyEntity +from ..tuya.const import unit_alias +from ...helpers.config_validation import currency + + +def _type_to_icon(account: Any) -> str: + """Return icon mappings - in the case that an account does not have a "logoURL" set - this is a subset of the 86 possible combos.""" + account_type = account["type"]["name"] + account_subtype = account["subtype"]["name"] + + icon_mapping = { + "brokerage": { + "brokerage": "mdi:chart-line", + "cryptocurrency": "mdi:currency-btc", + "ira": "mdi:bank", + "st_401a": "mdi:chart-bell-curve-cumulative", + "st_403b": "mdi:chart-bell-curve-cumulative", + "st_529": "mdi:school-outline", + }, + "credit": {"credit_card": "mdi:credit-card-outline"}, + "depository": { + "cash_management": "mdi:cash", + "checking": "mdi:checkbook", + "savings": "mdi:piggy-bank-outline", + "money_market": "mdi:piggy-bank-outline", + }, + "loan": { + "line_of_credit": "mdi:credit-card-plus-outline", + "loan": "mdi:bank-outline", + "mortgage": "mdi:home-city-outline", + }, + } + + default_icons = { + "brokerage": "mdi:chart-line", + "credit": "mdi:credit-card-outline", + "depository": "mdi:cash", + "loan": "mdi:bank-outline", + } + if account_subtype not in icon_mapping.get(account_type, {}): + LOGGER.info( + f"Unknown subtype '{account_subtype}' for account type '{account_type}'" + ) + return default_icons.get(account_type, "mdi:cash") + + return icon_mapping.get(account_type, {}).get( + account_subtype, default_icons.get(account_type, "mdi:cash") + ) + + +@dataclass(frozen=True, kw_only=True) +class MonarchMoneySensorEntityDescription(SensorEntityDescription): + """Describe a sensor entity.""" + + value_fn: Callable[[Any], StateType | datetime] + picture_fn: Callable[[Any], str] | None = None + icon_fn: Callable[[Any], str] | None = None + + +MONARCH_MONEY_SENSORS: tuple[MonarchMoneySensorEntityDescription, ...] = ( + MonarchMoneySensorEntityDescription( + key="currentBalance", + translation_key="balance", + state_class=SensorStateClass.TOTAL, + device_class=SensorDeviceClass.MONETARY, + value_fn=lambda account: account["currentBalance"], + picture_fn=lambda account: account["logoUrl"], + icon_fn=lambda account: _type_to_icon(account), + ), + MonarchMoneySensorEntityDescription( + key="age", + translation_key="age", + device_class=SensorDeviceClass.TIMESTAMP, + entity_category=EntityCategory.DIAGNOSTIC, + value_fn=lambda account: datetime.fromisoformat(account["updatedAt"]), + ), + MonarchMoneySensorEntityDescription( + key="created", + translation_key="created", + device_class=SensorDeviceClass.TIMESTAMP, + entity_category=EntityCategory.DIAGNOSTIC, + value_fn=lambda account: datetime.fromisoformat(account["createdAt"]), + ), +) + + +class MonarchMoneySensor(MonarchMoneyEntity, SensorEntity): + """Define a monarch money sensor.""" + + entity_description: MonarchMoneySensorEntityDescription + + @property + def native_value(self) -> StateType | datetime | None: + """Return the state.""" + return self.entity_description.value_fn(self.account_data) + + @property + def entity_picture(self) -> str | None: + """Return the picture of the account as provided by monarch money if it exists.""" + if self.entity_description.picture_fn is not None: + return self.entity_description.picture_fn(self.account_data) + return None + + @property + def icon(self) -> str | None: + """Icon function.""" + if self.entity_description.icon_fn is not None: + return self.entity_description.icon_fn(self.account_data) + return None + + +async def async_setup_entry( + hass: HomeAssistant, + config_entry: MonarchMoneyConfigEntry, + async_add_entities: AddEntitiesCallback, +) -> None: + """Set up Monarch Money sensors for config entries.""" + mm_coordinator = config_entry.runtime_data + + async_add_entities( + MonarchMoneySensor( + mm_coordinator, + sensor_description, + account, + ) + for account in mm_coordinator.accounts + for sensor_description in MONARCH_MONEY_SENSORS + ) diff --git a/homeassistant/components/monarchmoney/strings.json b/homeassistant/components/monarchmoney/strings.json new file mode 100644 index 00000000000000..071af39d5295bb --- /dev/null +++ b/homeassistant/components/monarchmoney/strings.json @@ -0,0 +1,32 @@ +{ + "config": { + "step": { + "user": { + "data": { + "mfa_secret": "Add your MFA Secret. See docs for help.", + "email": "[%key:common::config_flow::data::email%]", + "password": "[%key:common::config_flow::data::password%]" + } + } + }, + "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%]" + }, + "abort": { + "already_configured": "[%key:common::config_flow::abort::already_configured_device%]" + } + }, + "entity": { + "sensor": { + "balance": { "name": "Balance" }, + "age": { + "name": "Data age" + }, + "created": { + "name": "First sync" + } + } + } +} diff --git a/homeassistant/generated/config_flows.py b/homeassistant/generated/config_flows.py index 2e38d608bd96d6..3d94efef59fc6e 100644 --- a/homeassistant/generated/config_flows.py +++ b/homeassistant/generated/config_flows.py @@ -368,6 +368,7 @@ "modem_callerid", "modern_forms", "moehlenhoff_alpha2", + "monarchmoney", "monoprice", "monzo", "moon", diff --git a/homeassistant/generated/integrations.json b/homeassistant/generated/integrations.json index cd37adc3f7163f..09c3cb65dfbbbc 100644 --- a/homeassistant/generated/integrations.json +++ b/homeassistant/generated/integrations.json @@ -3790,6 +3790,18 @@ "config_flow": true, "iot_class": "local_push" }, + "mold_indicator": { + "name": "Mold Indicator", + "integration_type": "hub", + "config_flow": false, + "iot_class": "local_polling" + }, + "monarchmoney": { + "name": "Monarch Money", + "integration_type": "hub", + "config_flow": true, + "iot_class": "cloud_polling" + }, "monessen": { "name": "Monessen", "integration_type": "virtual", diff --git a/requirements_all.txt b/requirements_all.txt index eb1c8a21932154..a0ed435f6475de 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1368,6 +1368,9 @@ moat-ble==0.1.1 # homeassistant.components.moehlenhoff_alpha2 moehlenhoff-alpha2==1.3.1 +# homeassistant.components.monarchmoney +monarchmoney==0.1.13 + # homeassistant.components.monzo monzopy==1.3.2 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index fb2d4931173aba..aad61890d55656 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1137,6 +1137,9 @@ moat-ble==0.1.1 # homeassistant.components.moehlenhoff_alpha2 moehlenhoff-alpha2==1.3.1 +# homeassistant.components.monarchmoney +monarchmoney==0.1.13 + # homeassistant.components.monzo monzopy==1.3.2 diff --git a/tests/components/monarchmoney/__init__.py b/tests/components/monarchmoney/__init__.py new file mode 100644 index 00000000000000..939f1099d52c21 --- /dev/null +++ b/tests/components/monarchmoney/__init__.py @@ -0,0 +1 @@ +"""Tests for the Monarch Money integration.""" diff --git a/tests/components/monarchmoney/conftest.py b/tests/components/monarchmoney/conftest.py new file mode 100644 index 00000000000000..185fc5745ea2f9 --- /dev/null +++ b/tests/components/monarchmoney/conftest.py @@ -0,0 +1,47 @@ +"""Common fixtures for the Monarch Money tests.""" + +from collections.abc import Generator +from unittest.mock import AsyncMock, patch, PropertyMock + +import pytest + +from homeassistant.const import CONF_TOKEN +from tests.common import MockConfigEntry +from homeassistant.components.monarchmoney.const import DOMAIN + +@pytest.fixture +def mock_setup_entry() -> Generator[AsyncMock]: + """Override async_setup_entry.""" + with patch( + "homeassistant.components.monarchmoney.async_setup_entry", return_value=True + ) as mock_setup_entry: + yield mock_setup_entry + +@pytest.fixture +async def mock_config_entry() -> MockConfigEntry: + """Fixture for mock config entry.""" + return MockConfigEntry( + domain=DOMAIN, + data={CONF_TOKEN: "fake_token_of_doom"}, + version=1, + ) + + +@pytest.fixture +def mock_api() -> Generator[AsyncMock]: + """Mock the MonarchMoney class.""" + with patch("homeassistant.components.monarchmoney.config_flow.MonarchMoney", autospec=True) as mock_class: + instance = mock_class.return_value + type(instance).token = PropertyMock(return_value="mocked_token") + instance.login = AsyncMock(return_value=None) + instance.get_subscription_details = AsyncMock(return_value={ + 'subscription': { + 'id': '123456789', + 'paymentSource': 'STRIPE', + 'referralCode': 'go3dpvrdmw', + 'isOnFreeTrial': False, + 'hasPremiumEntitlement': True, + '__typename': 'HouseholdSubscription' + } + }) + yield mock_class \ No newline at end of file diff --git a/tests/components/monarchmoney/fixtures/get_accounts.json b/tests/components/monarchmoney/fixtures/get_accounts.json new file mode 100644 index 00000000000000..ac28c3f672ef26 --- /dev/null +++ b/tests/components/monarchmoney/fixtures/get_accounts.json @@ -0,0 +1,429 @@ +{ + "accounts": [ + { + "id": "900000000", + "displayName": "Brokerage", + "syncDisabled": false, + "deactivatedAt": null, + "isHidden": false, + "isAsset": true, + "mask": "0189", + "createdAt": "2021-10-15T01:32:33.809450+00:00", + "updatedAt": "2022-05-26T00:56:41.322045+00:00", + "displayLastUpdatedAt": "2022-05-26T00:56:41.321928+00:00", + "currentBalance": 1000.5, + "displayBalance": 1000.5, + "includeInNetWorth": true, + "hideFromList": true, + "hideTransactionsFromReports": false, + "includeBalanceInNetWorth": false, + "includeInGoalBalance": false, + "dataProvider": "plaid", + "dataProviderAccountId": "testProviderAccountId", + "isManual": false, + "transactionsCount": 0, + "holdingsCount": 0, + "manualInvestmentsTrackingMethod": null, + "order": 11, + "icon": "trending-up", + "logoUrl": "base64Nonce", + "type": { + "name": "brokerage", + "display": "Investments", + "__typename": "AccountType" + }, + "subtype": { + "name": "brokerage", + "display": "Brokerage", + "__typename": "AccountSubtype" + }, + "credential": { + "id": "900000001", + "updateRequired": false, + "disconnectedFromDataProviderAt": null, + "dataProvider": "PLAID", + "institution": { + "id": "700000000", + "plaidInstitutionId": "ins_0", + "name": "Rando Brokerage", + "status": "DEGRADED", + "logo": "base64Nonce", + "__typename": "Institution" + }, + "__typename": "Credential" + }, + "institution": { + "id": "700000000", + "name": "Rando Brokerage", + "logo": "base64Nonce", + "primaryColor": "#0075a3", + "url": "https://rando.brokerage/", + "__typename": "Institution" + }, + "__typename": "Account" + }, + { + "id": "900000002", + "displayName": "Checking", + "syncDisabled": false, + "deactivatedAt": null, + "isHidden": false, + "isAsset": true, + "mask": "2602", + "createdAt": "2021-10-15T01:32:33.900521+00:00", + "updatedAt": "2024-02-17T11:21:05.228959+00:00", + "displayLastUpdatedAt": "2024-02-17T11:21:05.228721+00:00", + "currentBalance": 1000.02, + "displayBalance": 1000.02, + "includeInNetWorth": true, + "hideFromList": false, + "hideTransactionsFromReports": false, + "includeBalanceInNetWorth": true, + "includeInGoalBalance": true, + "dataProvider": "plaid", + "dataProviderAccountId": "testProviderAccountId", + "isManual": false, + "transactionsCount": 1403, + "holdingsCount": 0, + "manualInvestmentsTrackingMethod": null, + "order": 0, + "icon": "dollar-sign", + "logoUrl": "data:image/png;base64,base64Nonce", + "type": { + "name": "depository", + "display": "Cash", + "__typename": "AccountType" + }, + "subtype": { + "name": "checking", + "display": "Checking", + "__typename": "AccountSubtype" + }, + "credential": { + "id": "900000003", + "updateRequired": false, + "disconnectedFromDataProviderAt": null, + "dataProvider": "PLAID", + "institution": { + "id": "7000000002", + "plaidInstitutionId": "ins_01", + "name": "Rando Bank", + "status": "DEGRADED", + "logo": "base64Nonce", + "__typename": "Institution" + }, + "__typename": "Credential" + }, + "institution": { + "id": "7000000005", + "name": "Rando Bank", + "logo": "base64Nonce", + "primaryColor": "#0075a3", + "url": "https://rando.bank/", + "__typename": "Institution" + }, + "__typename": "Account" + }, + { + "id": "9000000007", + "displayName": "Credit Card", + "syncDisabled": true, + "deactivatedAt": null, + "isHidden": true, + "isAsset": false, + "mask": "3542", + "createdAt": "2021-10-15T01:33:46.646459+00:00", + "updatedAt": "2022-12-10T18:17:06.129456+00:00", + "displayLastUpdatedAt": "2022-10-15T08:34:34.815239+00:00", + "currentBalance": -200.0, + "displayBalance": -200.0, + "includeInNetWorth": true, + "hideFromList": false, + "hideTransactionsFromReports": false, + "includeBalanceInNetWorth": false, + "includeInGoalBalance": true, + "dataProvider": "finicity", + "dataProviderAccountId": "50001", + "isManual": false, + "transactionsCount": 1138, + "holdingsCount": 0, + "manualInvestmentsTrackingMethod": null, + "order": 1, + "icon": "credit-card", + "logoUrl": "data:image/png;base64,base64Nonce", + "type": { + "name": "credit", + "display": "Credit Cards", + "__typename": "AccountType" + }, + "subtype": { + "name": "credit_card", + "display": "Credit Card", + "__typename": "AccountSubtype" + }, + "credential": { + "id": "9000000009", + "updateRequired": true, + "disconnectedFromDataProviderAt": null, + "dataProvider": "FINICITY", + "institution": { + "id": "7000000002", + "plaidInstitutionId": "ins_9", + "name": "Rando Credit", + "status": null, + "logo": "base64Nonce", + "__typename": "Institution" + }, + "__typename": "Credential" + }, + "institution": { + "id": "70000000010", + "name": "Rando Credit", + "logo": "base64Nonce", + "primaryColor": "#004966", + "url": "https://rando.credit/", + "__typename": "Institution" + }, + "__typename": "Account" + }, + { + "id": "900000000012", + "displayName": "Roth IRA", + "syncDisabled": false, + "deactivatedAt": null, + "isHidden": false, + "isAsset": true, + "mask": "1052", + "createdAt": "2021-10-15T01:35:59.299450+00:00", + "updatedAt": "2024-02-17T13:32:21.072711+00:00", + "displayLastUpdatedAt": "2024-02-17T13:32:21.072453+00:00", + "currentBalance": 10000.43, + "displayBalance": 10000.43, + "includeInNetWorth": true, + "hideFromList": false, + "hideTransactionsFromReports": false, + "includeBalanceInNetWorth": true, + "includeInGoalBalance": false, + "dataProvider": "plaid", + "dataProviderAccountId": "testProviderAccountId", + "isManual": false, + "transactionsCount": 28, + "holdingsCount": 24, + "manualInvestmentsTrackingMethod": null, + "order": 4, + "icon": "trending-up", + "logoUrl": "data:image/png;base64,base64Nonce", + "type": { + "name": "brokerage", + "display": "Investments", + "__typename": "AccountType" + }, + "subtype": { + "name": "roth", + "display": "Roth IRA", + "__typename": "AccountSubtype" + }, + "credential": { + "id": "90000000014", + "updateRequired": false, + "disconnectedFromDataProviderAt": null, + "dataProvider": "PLAID", + "institution": { + "id": "70000000016", + "plaidInstitutionId": "ins_02", + "name": "Rando Investments", + "status": null, + "logo": "base64Nonce", + "__typename": "Institution" + }, + "__typename": "Credential" + }, + "institution": { + "id": "70000000018", + "name": "Rando Investments", + "logo": "base64Nonce", + "primaryColor": "#40a829", + "url": "https://rando.investments/", + "__typename": "Institution" + }, + "__typename": "Account" + }, + { + "id": "90000000020", + "displayName": "House", + "syncDisabled": false, + "deactivatedAt": null, + "isHidden": false, + "isAsset": true, + "mask": null, + "createdAt": "2021-10-15T01:39:29.370279+00:00", + "updatedAt": "2024-02-12T09:00:25.451425+00:00", + "displayLastUpdatedAt": "2024-02-12T09:00:25.451425+00:00", + "currentBalance": 123000.0, + "displayBalance": 123000.0, + "includeInNetWorth": true, + "hideFromList": false, + "hideTransactionsFromReports": false, + "includeBalanceInNetWorth": true, + "includeInGoalBalance": false, + "dataProvider": "zillow", + "dataProviderAccountId": "testProviderAccountId", + "isManual": false, + "transactionsCount": 0, + "holdingsCount": 0, + "manualInvestmentsTrackingMethod": null, + "order": 2, + "icon": "home", + "logoUrl": "data:image/png;base64,base64Nonce", + "type": { + "name": "real_estate", + "display": "Real Estate", + "__typename": "AccountType" + }, + "subtype": { + "name": "primary_home", + "display": "Primary Home", + "__typename": "AccountSubtype" + }, + "credential": null, + "institution": { + "id": "800000000", + "name": "Zillow", + "logo": "base64Nonce", + "primaryColor": "#006AFF", + "url": "https://www.zillow.com/", + "__typename": "Institution" + }, + "__typename": "Account" + }, + { + "id": "90000000022", + "displayName": "401.k", + "syncDisabled": false, + "deactivatedAt": null, + "isHidden": false, + "isAsset": true, + "mask": null, + "createdAt": "2021-10-15T01:41:54.593239+00:00", + "updatedAt": "2024-02-17T08:13:10.554296+00:00", + "displayLastUpdatedAt": "2024-02-17T08:13:10.554029+00:00", + "currentBalance": 100000.35, + "displayBalance": 100000.35, + "includeInNetWorth": true, + "hideFromList": false, + "hideTransactionsFromReports": false, + "includeBalanceInNetWorth": true, + "includeInGoalBalance": false, + "dataProvider": "finicity", + "dataProviderAccountId": "testProviderAccountId", + "isManual": false, + "transactionsCount": 0, + "holdingsCount": 100, + "manualInvestmentsTrackingMethod": null, + "order": 3, + "icon": "trending-up", + "logoUrl": "data:image/png;base64,base64Nonce", + "type": { + "name": "brokerage", + "display": "Investments", + "__typename": "AccountType" + }, + "subtype": { + "name": "st_401k", + "display": "401k", + "__typename": "AccountSubtype" + }, + "credential": { + "id": "90000000024", + "updateRequired": false, + "disconnectedFromDataProviderAt": null, + "dataProvider": "FINICITY", + "institution": { + "id": "70000000026", + "plaidInstitutionId": "ins_03", + "name": "Rando Employer Investments", + "status": "HEALTHY", + "logo": "base64Nonce", + "__typename": "Institution" + }, + "__typename": "Credential" + }, + "institution": { + "id": "70000000028", + "name": "Rando Employer Investments", + "logo": "base64Nonce", + "primaryColor": "#408800", + "url": "https://rando-employer.investments/", + "__typename": "Institution" + }, + "__typename": "Account" + }, + { + "id": "90000000030", + "displayName": "Mortgage", + "syncDisabled": true, + "deactivatedAt": "2023-08-15", + "isHidden": true, + "isAsset": false, + "mask": "0973", + "createdAt": "2021-10-15T01:45:25.244570+00:00", + "updatedAt": "2023-08-16T01:41:36.115588+00:00", + "displayLastUpdatedAt": "2023-08-15T18:11:09.134874+00:00", + "currentBalance": 0.0, + "displayBalance": -0.0, + "includeInNetWorth": true, + "hideFromList": false, + "hideTransactionsFromReports": false, + "includeBalanceInNetWorth": false, + "includeInGoalBalance": false, + "dataProvider": "plaid", + "dataProviderAccountId": "testProviderAccountId", + "isManual": false, + "transactionsCount": 0, + "holdingsCount": 0, + "manualInvestmentsTrackingMethod": null, + "order": 1, + "icon": "home", + "logoUrl": "data:image/png;base64,base64Nonce", + "type": { + "name": "loan", + "display": "Loans", + "__typename": "AccountType" + }, + "subtype": { + "name": "mortgage", + "display": "Mortgage", + "__typename": "AccountSubtype" + }, + "credential": { + "id": "90000000032", + "updateRequired": false, + "disconnectedFromDataProviderAt": null, + "dataProvider": "PLAID", + "institution": { + "id": "70000000034", + "plaidInstitutionId": "ins_04", + "name": "Rando Mortgage", + "status": "HEALTHY", + "logo": "base64Nonce", + "__typename": "Institution" + }, + "__typename": "Credential" + }, + "institution": { + "id": "70000000036", + "name": "Rando Mortgage", + "logo": "base64Nonce", + "primaryColor": "#095aa6", + "url": "https://rando.mortgage/", + "__typename": "Institution" + }, + "__typename": "Account" + } + ], + "householdPreferences": { + "id": "900000000022", + "accountGroupOrder": [], + "__typename": "HouseholdPreferences" + } +} diff --git a/tests/components/monarchmoney/test_config_flow.py b/tests/components/monarchmoney/test_config_flow.py new file mode 100644 index 00000000000000..a618fee31148b6 --- /dev/null +++ b/tests/components/monarchmoney/test_config_flow.py @@ -0,0 +1,170 @@ +"""Test the Monarch Money config flow.""" + +from unittest.mock import AsyncMock, patch + +from homeassistant import config_entries +from homeassistant.components.monarchmoney.config_flow import InvalidAuth +from homeassistant.components.monarchmoney.const import DOMAIN, CONF_MFA_SECRET +from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME, CONF_EMAIL, CONF_TOKEN +from homeassistant.core import HomeAssistant +from homeassistant.data_entry_flow import FlowResultType + + + +async def test_form(hass: HomeAssistant, mock_setup_entry: AsyncMock, mock_api: AsyncMock) -> None: + """Test we get the form.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + assert result["type"] == FlowResultType.FORM + assert result["errors"] == {} + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + { + CONF_EMAIL: "test-username", + CONF_PASSWORD: "test-password", + CONF_MFA_SECRET: "test-mfa", + }, + ) + await hass.async_block_till_done() + + assert result["type"] == FlowResultType.CREATE_ENTRY + assert result["title"] == "Monarch Money" + assert result["data"] == { + CONF_TOKEN: "mocked_token", + } + assert len(mock_setup_entry.mock_calls) == 1 + +# async def test_form(hass: HomeAssistant, mock_setup_entry: AsyncMock) -> None: +# """Test we get the form.""" +# result = await hass.config_entries.flow.async_init( +# DOMAIN, context={"source": config_entries.SOURCE_USER} +# ) +# assert result["type"] == FlowResultType.FORM +# assert result["errors"] == {} +# +# with patch( +# "homeassistant.components.monarchmoney.config_flow.MonarchMoney.login", +# return_value=True, +# ), patch("homeassistant.components.monarchmoney.config_flow.MonarchMoney.token", return_value="1111"): +# result = await hass.config_entries.flow.async_configure( +# result["flow_id"], +# { +# CONF_EMAIL: "test-username", +# CONF_PASSWORD: "test-password", +# CONF_MFA_SECRET: "test-mfa", +# }, +# ) +# await hass.async_block_till_done() +# +# assert result["type"] == FlowResultType.CREATE_ENTRY +# assert result["title"] == "Monarch Money" +# assert result["data"] == { +# CONF_TOKEN: "1.1.1.1", +# +# } +# assert len(mock_setup_entry.mock_calls) == 1 + + +async def test_form_invalid_auth( + hass: HomeAssistant, mock_setup_entry: AsyncMock +) -> None: + """Test we handle invalid auth.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + + with patch( + "homeassistant.components.monarchmoney.config_flow.PlaceholderHub.authenticate", + side_effect=InvalidAuth, + ): + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + { + CONF_HOST: "1.1.1.1", + CONF_USERNAME: "test-username", + CONF_PASSWORD: "test-password", + }, + ) + + assert result["type"] == FlowResultType.FORM + assert result["errors"] == {"base": "invalid_auth"} + + # Make sure the config flow tests finish with either an + # FlowResultType.CREATE_ENTRY or FlowResultType.ABORT so + # we can show the config flow is able to recover from an error. + with patch( + "homeassistant.components.monarchmoney.config_flow.PlaceholderHub.authenticate", + return_value=True, + ): + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + { + CONF_HOST: "1.1.1.1", + CONF_USERNAME: "test-username", + CONF_PASSWORD: "test-password", + }, + ) + await hass.async_block_till_done() + + assert result["type"] == FlowResultType.CREATE_ENTRY + assert result["title"] == "Name of the device" + assert result["data"] == { + CONF_HOST: "1.1.1.1", + CONF_USERNAME: "test-username", + CONF_PASSWORD: "test-password", + } + assert len(mock_setup_entry.mock_calls) == 1 + + +async def test_form_cannot_connect( + hass: HomeAssistant, mock_setup_entry: AsyncMock +) -> None: + """Test we handle cannot connect error.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + + with patch( + "homeassistant.components.monarchmoney.config_flow.PlaceholderHub.authenticate", + side_effect=CannotConnect, + ): + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + { + CONF_HOST: "1.1.1.1", + CONF_USERNAME: "test-username", + CONF_PASSWORD: "test-password", + }, + ) + + assert result["type"] == FlowResultType.FORM + assert result["errors"] == {"base": "cannot_connect"} + + # Make sure the config flow tests finish with either an + # FlowResultType.CREATE_ENTRY or FlowResultType.ABORT so + # we can show the config flow is able to recover from an error. + + with patch( + "homeassistant.components.monarchmoney.config_flow.PlaceholderHub.authenticate", + return_value=True, + ): + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + { + CONF_HOST: "1.1.1.1", + CONF_USERNAME: "test-username", + CONF_PASSWORD: "test-password", + }, + ) + await hass.async_block_till_done() + + assert result["type"] == FlowResultType.CREATE_ENTRY + assert result["title"] == "Name of the device" + assert result["data"] == { + CONF_HOST: "1.1.1.1", + CONF_USERNAME: "test-username", + CONF_PASSWORD: "test-password", + } + assert len(mock_setup_entry.mock_calls) == 1 diff --git a/tests/components/monarchmoney/test_sensor.py b/tests/components/monarchmoney/test_sensor.py new file mode 100644 index 00000000000000..cf29e500a0b8a1 --- /dev/null +++ b/tests/components/monarchmoney/test_sensor.py @@ -0,0 +1 @@ +"""Test sensors.""" From 609aee255647d1ab542d34bb46739565ea0c997f Mon Sep 17 00:00:00 2001 From: Jeff Stein Date: Thu, 15 Aug 2024 21:20:39 -0600 Subject: [PATCH 02/67] Second commit - with some coverage but errors abount --- .../components/monarchmoney/coordinator.py | 1 + .../components/monarchmoney/entity.py | 5 +- .../components/monarchmoney/sensor.py | 2 - tests/components/monarchmoney/__init__.py | 12 + tests/components/monarchmoney/conftest.py | 47 +- .../monarchmoney/snapshots/test_sensor.ambr | 1044 +++++++++++++++++ .../monarchmoney/test_config_flow.py | 116 +- tests/components/monarchmoney/test_sensor.py | 54 + 8 files changed, 1209 insertions(+), 72 deletions(-) create mode 100644 tests/components/monarchmoney/snapshots/test_sensor.ambr diff --git a/homeassistant/components/monarchmoney/coordinator.py b/homeassistant/components/monarchmoney/coordinator.py index 602025b9366505..438ba2cf1354fa 100644 --- a/homeassistant/components/monarchmoney/coordinator.py +++ b/homeassistant/components/monarchmoney/coordinator.py @@ -36,6 +36,7 @@ async def _async_update_data(self) -> Any: @property def accounts(self) -> Any: """Return accounts.""" + return self.data["accounts"] def get_account_for_id(self, account_id: str) -> Any | None: diff --git a/homeassistant/components/monarchmoney/entity.py b/homeassistant/components/monarchmoney/entity.py index f227f18cfcd10c..5c7b9c3bb58246 100644 --- a/homeassistant/components/monarchmoney/entity.py +++ b/homeassistant/components/monarchmoney/entity.py @@ -29,7 +29,10 @@ def __init__( # Parse out some fields institution = account["institution"]["name"] - provider = account["credential"]["dataProvider"] + + provider = account.get("dataProvider", "Manual input") + if account.get("credential") is not None: + provider = account["credential"].get("dataProvider", provider) self._attr_attribution = f"Data provided by Monarch Money API via {provider}" diff --git a/homeassistant/components/monarchmoney/sensor.py b/homeassistant/components/monarchmoney/sensor.py index 6793674556bf16..2243549cc73768 100644 --- a/homeassistant/components/monarchmoney/sensor.py +++ b/homeassistant/components/monarchmoney/sensor.py @@ -19,8 +19,6 @@ from . import MonarchMoneyConfigEntry from .const import LOGGER from .entity import MonarchMoneyEntity -from ..tuya.const import unit_alias -from ...helpers.config_validation import currency def _type_to_icon(account: Any) -> str: diff --git a/tests/components/monarchmoney/__init__.py b/tests/components/monarchmoney/__init__.py index 939f1099d52c21..f08addf2ec6c67 100644 --- a/tests/components/monarchmoney/__init__.py +++ b/tests/components/monarchmoney/__init__.py @@ -1 +1,13 @@ """Tests for the Monarch Money integration.""" + +from homeassistant.core import HomeAssistant + +from tests.common import MockConfigEntry + + +async def setup_integration(hass: HomeAssistant, config_entry: MockConfigEntry) -> None: + """Fixture for setting up the component.""" + config_entry.add_to_hass(hass) + + await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() diff --git a/tests/components/monarchmoney/conftest.py b/tests/components/monarchmoney/conftest.py index 185fc5745ea2f9..a6ac2367daaa45 100644 --- a/tests/components/monarchmoney/conftest.py +++ b/tests/components/monarchmoney/conftest.py @@ -1,13 +1,17 @@ """Common fixtures for the Monarch Money tests.""" from collections.abc import Generator -from unittest.mock import AsyncMock, patch, PropertyMock +import json +from typing import Any +from unittest.mock import AsyncMock, PropertyMock, patch import pytest -from homeassistant.const import CONF_TOKEN -from tests.common import MockConfigEntry from homeassistant.components.monarchmoney.const import DOMAIN +from homeassistant.const import CONF_TOKEN + +from tests.common import MockConfigEntry, load_fixture + @pytest.fixture def mock_setup_entry() -> Generator[AsyncMock]: @@ -17,6 +21,7 @@ def mock_setup_entry() -> Generator[AsyncMock]: ) as mock_setup_entry: yield mock_setup_entry + @pytest.fixture async def mock_config_entry() -> MockConfigEntry: """Fixture for mock config entry.""" @@ -28,20 +33,32 @@ async def mock_config_entry() -> MockConfigEntry: @pytest.fixture -def mock_api() -> Generator[AsyncMock]: +def mock_config_api() -> Generator[AsyncMock]: """Mock the MonarchMoney class.""" - with patch("homeassistant.components.monarchmoney.config_flow.MonarchMoney", autospec=True) as mock_class: + + account_data: dict[str, Any] = json.loads(load_fixture("get_accounts.json", DOMAIN)) + + with ( + patch( + "homeassistant.components.monarchmoney.config_flow.MonarchMoney", + autospec=True, + ) as mock_class, + patch("homeassistant.components.monarchmoney.MonarchMoney", new=mock_class), + ): instance = mock_class.return_value type(instance).token = PropertyMock(return_value="mocked_token") instance.login = AsyncMock(return_value=None) - instance.get_subscription_details = AsyncMock(return_value={ - 'subscription': { - 'id': '123456789', - 'paymentSource': 'STRIPE', - 'referralCode': 'go3dpvrdmw', - 'isOnFreeTrial': False, - 'hasPremiumEntitlement': True, - '__typename': 'HouseholdSubscription' + instance.get_subscription_details = AsyncMock( + return_value={ + "subscription": { + "id": "123456789", + "paymentSource": "STRIPE", + "referralCode": "go3dpvrdmw", + "isOnFreeTrial": False, + "hasPremiumEntitlement": True, + "__typename": "HouseholdSubscription", + } } - }) - yield mock_class \ No newline at end of file + ) + instance.get_accounts = AsyncMock(return_value=account_data) + yield mock_class diff --git a/tests/components/monarchmoney/snapshots/test_sensor.ambr b/tests/components/monarchmoney/snapshots/test_sensor.ambr new file mode 100644 index 00000000000000..4dc9a9769e678e --- /dev/null +++ b/tests/components/monarchmoney/snapshots/test_sensor.ambr @@ -0,0 +1,1044 @@ +# serializer version: 1 +# name: test_all_entities[sensor.rando_bank_checking_balance-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.rando_bank_checking_balance', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': 'mdi:checkbook', + 'original_name': 'Balance', + 'platform': 'monarchmoney', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'balance', + 'unique_id': 'Rando Bank_Checking_balance', + 'unit_of_measurement': None, + }) +# --- +# name: test_all_entities[sensor.rando_bank_checking_balance-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'attribution': 'Data provided by Monarch Money API via PLAID', + 'device_class': 'monetary', + 'entity_picture': 'data:image/png;base64,base64Nonce', + 'friendly_name': 'Rando Bank Checking Balance', + 'icon': 'mdi:checkbook', + 'state_class': , + }), + 'context': , + 'entity_id': 'sensor.rando_bank_checking_balance', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '1000.02', + }) +# --- +# name: test_all_entities[sensor.rando_bank_checking_data_age-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': , + 'entity_id': 'sensor.rando_bank_checking_data_age', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Data age', + 'platform': 'monarchmoney', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'age', + 'unique_id': 'Rando Bank_Checking_age', + 'unit_of_measurement': None, + }) +# --- +# name: test_all_entities[sensor.rando_bank_checking_data_age-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'attribution': 'Data provided by Monarch Money API via PLAID', + 'device_class': 'timestamp', + 'friendly_name': 'Rando Bank Checking Data age', + }), + 'context': , + 'entity_id': 'sensor.rando_bank_checking_data_age', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '2024-02-17T11:21:05+00:00', + }) +# --- +# name: test_all_entities[sensor.rando_bank_checking_first_sync-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': , + 'entity_id': 'sensor.rando_bank_checking_first_sync', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'First sync', + 'platform': 'monarchmoney', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'created', + 'unique_id': 'Rando Bank_Checking_created', + 'unit_of_measurement': None, + }) +# --- +# name: test_all_entities[sensor.rando_bank_checking_first_sync-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'attribution': 'Data provided by Monarch Money API via PLAID', + 'device_class': 'timestamp', + 'friendly_name': 'Rando Bank Checking First sync', + }), + 'context': , + 'entity_id': 'sensor.rando_bank_checking_first_sync', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '2021-10-15T01:32:33+00:00', + }) +# --- +# name: test_all_entities[sensor.rando_brokerage_brokerage_balance-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.rando_brokerage_brokerage_balance', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': 'mdi:chart-line', + 'original_name': 'Balance', + 'platform': 'monarchmoney', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'balance', + 'unique_id': 'Rando Brokerage_Brokerage_balance', + 'unit_of_measurement': None, + }) +# --- +# name: test_all_entities[sensor.rando_brokerage_brokerage_balance-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'attribution': 'Data provided by Monarch Money API via PLAID', + 'device_class': 'monetary', + 'entity_picture': 'base64Nonce', + 'friendly_name': 'Rando Brokerage Brokerage Balance', + 'icon': 'mdi:chart-line', + 'state_class': , + }), + 'context': , + 'entity_id': 'sensor.rando_brokerage_brokerage_balance', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '1000.5', + }) +# --- +# name: test_all_entities[sensor.rando_brokerage_brokerage_data_age-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': , + 'entity_id': 'sensor.rando_brokerage_brokerage_data_age', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Data age', + 'platform': 'monarchmoney', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'age', + 'unique_id': 'Rando Brokerage_Brokerage_age', + 'unit_of_measurement': None, + }) +# --- +# name: test_all_entities[sensor.rando_brokerage_brokerage_data_age-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'attribution': 'Data provided by Monarch Money API via PLAID', + 'device_class': 'timestamp', + 'friendly_name': 'Rando Brokerage Brokerage Data age', + }), + 'context': , + 'entity_id': 'sensor.rando_brokerage_brokerage_data_age', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '2022-05-26T00:56:41+00:00', + }) +# --- +# name: test_all_entities[sensor.rando_brokerage_brokerage_first_sync-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': , + 'entity_id': 'sensor.rando_brokerage_brokerage_first_sync', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'First sync', + 'platform': 'monarchmoney', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'created', + 'unique_id': 'Rando Brokerage_Brokerage_created', + 'unit_of_measurement': None, + }) +# --- +# name: test_all_entities[sensor.rando_brokerage_brokerage_first_sync-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'attribution': 'Data provided by Monarch Money API via PLAID', + 'device_class': 'timestamp', + 'friendly_name': 'Rando Brokerage Brokerage First sync', + }), + 'context': , + 'entity_id': 'sensor.rando_brokerage_brokerage_first_sync', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '2021-10-15T01:32:33+00:00', + }) +# --- +# name: test_all_entities[sensor.rando_credit_credit_card_balance-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.rando_credit_credit_card_balance', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': 'mdi:credit-card-outline', + 'original_name': 'Balance', + 'platform': 'monarchmoney', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'balance', + 'unique_id': 'Rando Credit_Credit Card_balance', + 'unit_of_measurement': None, + }) +# --- +# name: test_all_entities[sensor.rando_credit_credit_card_balance-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'attribution': 'Data provided by Monarch Money API via FINICITY', + 'device_class': 'monetary', + 'entity_picture': 'data:image/png;base64,base64Nonce', + 'friendly_name': 'Rando Credit Credit Card Balance', + 'icon': 'mdi:credit-card-outline', + 'state_class': , + }), + 'context': , + 'entity_id': 'sensor.rando_credit_credit_card_balance', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '-200.0', + }) +# --- +# name: test_all_entities[sensor.rando_credit_credit_card_data_age-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': , + 'entity_id': 'sensor.rando_credit_credit_card_data_age', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Data age', + 'platform': 'monarchmoney', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'age', + 'unique_id': 'Rando Credit_Credit Card_age', + 'unit_of_measurement': None, + }) +# --- +# name: test_all_entities[sensor.rando_credit_credit_card_data_age-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'attribution': 'Data provided by Monarch Money API via FINICITY', + 'device_class': 'timestamp', + 'friendly_name': 'Rando Credit Credit Card Data age', + }), + 'context': , + 'entity_id': 'sensor.rando_credit_credit_card_data_age', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '2022-12-10T18:17:06+00:00', + }) +# --- +# name: test_all_entities[sensor.rando_credit_credit_card_first_sync-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': , + 'entity_id': 'sensor.rando_credit_credit_card_first_sync', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'First sync', + 'platform': 'monarchmoney', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'created', + 'unique_id': 'Rando Credit_Credit Card_created', + 'unit_of_measurement': None, + }) +# --- +# name: test_all_entities[sensor.rando_credit_credit_card_first_sync-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'attribution': 'Data provided by Monarch Money API via FINICITY', + 'device_class': 'timestamp', + 'friendly_name': 'Rando Credit Credit Card First sync', + }), + 'context': , + 'entity_id': 'sensor.rando_credit_credit_card_first_sync', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '2021-10-15T01:33:46+00:00', + }) +# --- +# name: test_all_entities[sensor.rando_employer_investments_401_k_balance-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.rando_employer_investments_401_k_balance', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': 'mdi:chart-line', + 'original_name': 'Balance', + 'platform': 'monarchmoney', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'balance', + 'unique_id': 'Rando Employer Investments_401.k_balance', + 'unit_of_measurement': None, + }) +# --- +# name: test_all_entities[sensor.rando_employer_investments_401_k_balance-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'attribution': 'Data provided by Monarch Money API via FINICITY', + 'device_class': 'monetary', + 'entity_picture': 'data:image/png;base64,base64Nonce', + 'friendly_name': 'Rando Employer Investments 401.k Balance', + 'icon': 'mdi:chart-line', + 'state_class': , + }), + 'context': , + 'entity_id': 'sensor.rando_employer_investments_401_k_balance', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '100000.35', + }) +# --- +# name: test_all_entities[sensor.rando_employer_investments_401_k_data_age-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': , + 'entity_id': 'sensor.rando_employer_investments_401_k_data_age', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Data age', + 'platform': 'monarchmoney', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'age', + 'unique_id': 'Rando Employer Investments_401.k_age', + 'unit_of_measurement': None, + }) +# --- +# name: test_all_entities[sensor.rando_employer_investments_401_k_data_age-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'attribution': 'Data provided by Monarch Money API via FINICITY', + 'device_class': 'timestamp', + 'friendly_name': 'Rando Employer Investments 401.k Data age', + }), + 'context': , + 'entity_id': 'sensor.rando_employer_investments_401_k_data_age', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '2024-02-17T08:13:10+00:00', + }) +# --- +# name: test_all_entities[sensor.rando_employer_investments_401_k_first_sync-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': , + 'entity_id': 'sensor.rando_employer_investments_401_k_first_sync', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'First sync', + 'platform': 'monarchmoney', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'created', + 'unique_id': 'Rando Employer Investments_401.k_created', + 'unit_of_measurement': None, + }) +# --- +# name: test_all_entities[sensor.rando_employer_investments_401_k_first_sync-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'attribution': 'Data provided by Monarch Money API via FINICITY', + 'device_class': 'timestamp', + 'friendly_name': 'Rando Employer Investments 401.k First sync', + }), + 'context': , + 'entity_id': 'sensor.rando_employer_investments_401_k_first_sync', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '2021-10-15T01:41:54+00:00', + }) +# --- +# name: test_all_entities[sensor.rando_investments_roth_ira_balance-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.rando_investments_roth_ira_balance', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': 'mdi:chart-line', + 'original_name': 'Balance', + 'platform': 'monarchmoney', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'balance', + 'unique_id': 'Rando Investments_Roth IRA_balance', + 'unit_of_measurement': None, + }) +# --- +# name: test_all_entities[sensor.rando_investments_roth_ira_balance-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'attribution': 'Data provided by Monarch Money API via PLAID', + 'device_class': 'monetary', + 'entity_picture': 'data:image/png;base64,base64Nonce', + 'friendly_name': 'Rando Investments Roth IRA Balance', + 'icon': 'mdi:chart-line', + 'state_class': , + }), + 'context': , + 'entity_id': 'sensor.rando_investments_roth_ira_balance', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '10000.43', + }) +# --- +# name: test_all_entities[sensor.rando_investments_roth_ira_data_age-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': , + 'entity_id': 'sensor.rando_investments_roth_ira_data_age', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Data age', + 'platform': 'monarchmoney', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'age', + 'unique_id': 'Rando Investments_Roth IRA_age', + 'unit_of_measurement': None, + }) +# --- +# name: test_all_entities[sensor.rando_investments_roth_ira_data_age-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'attribution': 'Data provided by Monarch Money API via PLAID', + 'device_class': 'timestamp', + 'friendly_name': 'Rando Investments Roth IRA Data age', + }), + 'context': , + 'entity_id': 'sensor.rando_investments_roth_ira_data_age', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '2024-02-17T13:32:21+00:00', + }) +# --- +# name: test_all_entities[sensor.rando_investments_roth_ira_first_sync-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': , + 'entity_id': 'sensor.rando_investments_roth_ira_first_sync', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'First sync', + 'platform': 'monarchmoney', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'created', + 'unique_id': 'Rando Investments_Roth IRA_created', + 'unit_of_measurement': None, + }) +# --- +# name: test_all_entities[sensor.rando_investments_roth_ira_first_sync-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'attribution': 'Data provided by Monarch Money API via PLAID', + 'device_class': 'timestamp', + 'friendly_name': 'Rando Investments Roth IRA First sync', + }), + 'context': , + 'entity_id': 'sensor.rando_investments_roth_ira_first_sync', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '2021-10-15T01:35:59+00:00', + }) +# --- +# name: test_all_entities[sensor.rando_mortgage_mortgage_balance-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.rando_mortgage_mortgage_balance', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': 'mdi:home-city-outline', + 'original_name': 'Balance', + 'platform': 'monarchmoney', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'balance', + 'unique_id': 'Rando Mortgage_Mortgage_balance', + 'unit_of_measurement': None, + }) +# --- +# name: test_all_entities[sensor.rando_mortgage_mortgage_balance-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'attribution': 'Data provided by Monarch Money API via PLAID', + 'device_class': 'monetary', + 'entity_picture': 'data:image/png;base64,base64Nonce', + 'friendly_name': 'Rando Mortgage Mortgage Balance', + 'icon': 'mdi:home-city-outline', + 'state_class': , + }), + 'context': , + 'entity_id': 'sensor.rando_mortgage_mortgage_balance', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '0.0', + }) +# --- +# name: test_all_entities[sensor.rando_mortgage_mortgage_data_age-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': , + 'entity_id': 'sensor.rando_mortgage_mortgage_data_age', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Data age', + 'platform': 'monarchmoney', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'age', + 'unique_id': 'Rando Mortgage_Mortgage_age', + 'unit_of_measurement': None, + }) +# --- +# name: test_all_entities[sensor.rando_mortgage_mortgage_data_age-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'attribution': 'Data provided by Monarch Money API via PLAID', + 'device_class': 'timestamp', + 'friendly_name': 'Rando Mortgage Mortgage Data age', + }), + 'context': , + 'entity_id': 'sensor.rando_mortgage_mortgage_data_age', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '2023-08-16T01:41:36+00:00', + }) +# --- +# name: test_all_entities[sensor.rando_mortgage_mortgage_first_sync-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': , + 'entity_id': 'sensor.rando_mortgage_mortgage_first_sync', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'First sync', + 'platform': 'monarchmoney', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'created', + 'unique_id': 'Rando Mortgage_Mortgage_created', + 'unit_of_measurement': None, + }) +# --- +# name: test_all_entities[sensor.rando_mortgage_mortgage_first_sync-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'attribution': 'Data provided by Monarch Money API via PLAID', + 'device_class': 'timestamp', + 'friendly_name': 'Rando Mortgage Mortgage First sync', + }), + 'context': , + 'entity_id': 'sensor.rando_mortgage_mortgage_first_sync', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '2021-10-15T01:45:25+00:00', + }) +# --- +# name: test_all_entities[sensor.zillow_house_balance-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.zillow_house_balance', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': 'mdi:cash', + 'original_name': 'Balance', + 'platform': 'monarchmoney', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'balance', + 'unique_id': 'Zillow_House_balance', + 'unit_of_measurement': None, + }) +# --- +# name: test_all_entities[sensor.zillow_house_balance-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'attribution': 'Data provided by Monarch Money API via zillow', + 'device_class': 'monetary', + 'entity_picture': 'data:image/png;base64,base64Nonce', + 'friendly_name': 'Zillow House Balance', + 'icon': 'mdi:cash', + 'state_class': , + }), + 'context': , + 'entity_id': 'sensor.zillow_house_balance', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '123000.0', + }) +# --- +# name: test_all_entities[sensor.zillow_house_data_age-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': , + 'entity_id': 'sensor.zillow_house_data_age', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Data age', + 'platform': 'monarchmoney', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'age', + 'unique_id': 'Zillow_House_age', + 'unit_of_measurement': None, + }) +# --- +# name: test_all_entities[sensor.zillow_house_data_age-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'attribution': 'Data provided by Monarch Money API via zillow', + 'device_class': 'timestamp', + 'friendly_name': 'Zillow House Data age', + }), + 'context': , + 'entity_id': 'sensor.zillow_house_data_age', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '2024-02-12T09:00:25+00:00', + }) +# --- +# name: test_all_entities[sensor.zillow_house_first_sync-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': , + 'entity_id': 'sensor.zillow_house_first_sync', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'First sync', + 'platform': 'monarchmoney', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'created', + 'unique_id': 'Zillow_House_created', + 'unit_of_measurement': None, + }) +# --- +# name: test_all_entities[sensor.zillow_house_first_sync-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'attribution': 'Data provided by Monarch Money API via zillow', + 'device_class': 'timestamp', + 'friendly_name': 'Zillow House First sync', + }), + 'context': , + 'entity_id': 'sensor.zillow_house_first_sync', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '2021-10-15T01:39:29+00:00', + }) +# --- diff --git a/tests/components/monarchmoney/test_config_flow.py b/tests/components/monarchmoney/test_config_flow.py index a618fee31148b6..47b238d49c21a9 100644 --- a/tests/components/monarchmoney/test_config_flow.py +++ b/tests/components/monarchmoney/test_config_flow.py @@ -4,14 +4,21 @@ from homeassistant import config_entries from homeassistant.components.monarchmoney.config_flow import InvalidAuth -from homeassistant.components.monarchmoney.const import DOMAIN, CONF_MFA_SECRET -from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME, CONF_EMAIL, CONF_TOKEN +from homeassistant.components.monarchmoney.const import CONF_MFA_SECRET, DOMAIN +from homeassistant.const import ( + CONF_EMAIL, + CONF_HOST, + CONF_PASSWORD, + CONF_TOKEN, + CONF_USERNAME, +) from homeassistant.core import HomeAssistant from homeassistant.data_entry_flow import FlowResultType - -async def test_form(hass: HomeAssistant, mock_setup_entry: AsyncMock, mock_api: AsyncMock) -> None: +async def test_form( + hass: HomeAssistant, mock_setup_entry: AsyncMock, mock_config_api: AsyncMock +) -> None: """Test we get the form.""" result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_USER} @@ -36,6 +43,7 @@ async def test_form(hass: HomeAssistant, mock_setup_entry: AsyncMock, mock_api: } assert len(mock_setup_entry.mock_calls) == 1 + # async def test_form(hass: HomeAssistant, mock_setup_entry: AsyncMock) -> None: # """Test we get the form.""" # result = await hass.config_entries.flow.async_init( @@ -118,53 +126,53 @@ async def test_form_invalid_auth( assert len(mock_setup_entry.mock_calls) == 1 -async def test_form_cannot_connect( - hass: HomeAssistant, mock_setup_entry: AsyncMock -) -> None: - """Test we handle cannot connect error.""" - result = await hass.config_entries.flow.async_init( - DOMAIN, context={"source": config_entries.SOURCE_USER} - ) - - with patch( - "homeassistant.components.monarchmoney.config_flow.PlaceholderHub.authenticate", - side_effect=CannotConnect, - ): - result = await hass.config_entries.flow.async_configure( - result["flow_id"], - { - CONF_HOST: "1.1.1.1", - CONF_USERNAME: "test-username", - CONF_PASSWORD: "test-password", - }, - ) - - assert result["type"] == FlowResultType.FORM - assert result["errors"] == {"base": "cannot_connect"} - - # Make sure the config flow tests finish with either an - # FlowResultType.CREATE_ENTRY or FlowResultType.ABORT so - # we can show the config flow is able to recover from an error. - - with patch( - "homeassistant.components.monarchmoney.config_flow.PlaceholderHub.authenticate", - return_value=True, - ): - result = await hass.config_entries.flow.async_configure( - result["flow_id"], - { - CONF_HOST: "1.1.1.1", - CONF_USERNAME: "test-username", - CONF_PASSWORD: "test-password", - }, - ) - await hass.async_block_till_done() - - assert result["type"] == FlowResultType.CREATE_ENTRY - assert result["title"] == "Name of the device" - assert result["data"] == { - CONF_HOST: "1.1.1.1", - CONF_USERNAME: "test-username", - CONF_PASSWORD: "test-password", - } - assert len(mock_setup_entry.mock_calls) == 1 +# async def test_form_cannot_connect( +# hass: HomeAssistant, mock_setup_entry: AsyncMock +# ) -> None: +# """Test we handle cannot connect error.""" +# result = await hass.config_entries.flow.async_init( +# DOMAIN, context={"source": config_entries.SOURCE_USER} +# ) +# +# with patch( +# "homeassistant.components.monarchmoney.config_flow.PlaceholderHub.authenticate", +# side_effect=CannotConnect, +# ): +# result = await hass.config_entries.flow.async_configure( +# result["flow_id"], +# { +# CONF_HOST: "1.1.1.1", +# CONF_USERNAME: "test-username", +# CONF_PASSWORD: "test-password", +# }, +# ) +# +# assert result["type"] == FlowResultType.FORM +# assert result["errors"] == {"base": "cannot_connect"} +# +# # Make sure the config flow tests finish with either an +# # FlowResultType.CREATE_ENTRY or FlowResultType.ABORT so +# # we can show the config flow is able to recover from an error. +# +# with patch( +# "homeassistant.components.monarchmoney.config_flow.PlaceholderHub.authenticate", +# return_value=True, +# ): +# result = await hass.config_entries.flow.async_configure( +# result["flow_id"], +# { +# CONF_HOST: "1.1.1.1", +# CONF_USERNAME: "test-username", +# CONF_PASSWORD: "test-password", +# }, +# ) +# await hass.async_block_till_done() +# +# assert result["type"] == FlowResultType.CREATE_ENTRY +# assert result["title"] == "Name of the device" +# assert result["data"] == { +# CONF_HOST: "1.1.1.1", +# CONF_USERNAME: "test-username", +# CONF_PASSWORD: "test-password", +# } +# assert len(mock_setup_entry.mock_calls) == 1 diff --git a/tests/components/monarchmoney/test_sensor.py b/tests/components/monarchmoney/test_sensor.py index cf29e500a0b8a1..77e67ed1de69c7 100644 --- a/tests/components/monarchmoney/test_sensor.py +++ b/tests/components/monarchmoney/test_sensor.py @@ -1 +1,55 @@ """Test sensors.""" + +from unittest.mock import AsyncMock, patch + +from syrupy import SnapshotAssertion + +from homeassistant.const import Platform +from homeassistant.core import HomeAssistant +from homeassistant.helpers import entity_registry as er + +from . import setup_integration + +from tests.common import MockConfigEntry, snapshot_platform + + +async def test_all_entities( + hass: HomeAssistant, + snapshot: SnapshotAssertion, + mock_config_entry: MockConfigEntry, + entity_registry: er.EntityRegistry, + mock_config_api: AsyncMock, +) -> None: + """Test all entities.""" + with patch("homeassistant.components.monarchmoney.PLATFORMS", [Platform.SENSOR]): + await setup_integration(hass, mock_config_entry) + + await snapshot_platform(hass, entity_registry, snapshot, mock_config_entry.entry_id) + + +# @pytest.mark.asyncio +# async def test_login(mock_api): +# """Test the login method of the MonarchMoney class.""" +# # Arrange +# monarch_client = mock_api +# +# # Act +# try: +# await monarch_client.login( +# email="test@example.com", +# password="password", +# save_session=False, +# use_saved_session=False, +# mfa_secret_key="mfa_secret_key", +# ) +# except LoginFailedException as exc: +# raise LoginFailedException from exc +# +# # Assert +# monarch_client.login.assert_awaited_once_with( +# email="test@example.com", +# password="password", +# save_session=False, +# use_saved_session=False, +# mfa_secret_key="mfa_secret_key", +# ) From 6e0d7b2a12e674a797a82d795056d54bf7df1f3b Mon Sep 17 00:00:00 2001 From: Jeff Stein Date: Thu, 15 Aug 2024 22:10:32 -0600 Subject: [PATCH 03/67] Updated testing coverage --- .../components/monarchmoney/config_flow.py | 100 +++++++-- .../components/monarchmoney/const.py | 1 + .../components/monarchmoney/strings.json | 5 +- tests/components/monarchmoney/conftest.py | 1 + .../monarchmoney/test_config_flow.py | 209 ++++++++++++------ 5 files changed, 232 insertions(+), 84 deletions(-) diff --git a/homeassistant/components/monarchmoney/config_flow.py b/homeassistant/components/monarchmoney/config_flow.py index 5c25eeb761dadd..e2cd41ce266457 100644 --- a/homeassistant/components/monarchmoney/config_flow.py +++ b/homeassistant/components/monarchmoney/config_flow.py @@ -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 @@ -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}") @@ -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: @@ -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.""" diff --git a/homeassistant/components/monarchmoney/const.py b/homeassistant/components/monarchmoney/const.py index be408219d7641e..d15ac09d153e3f 100644 --- a/homeassistant/components/monarchmoney/const.py +++ b/homeassistant/components/monarchmoney/const.py @@ -7,3 +7,4 @@ LOGGER = logging.getLogger(__package__) CONF_MFA_SECRET = "mfa_secret" +CONF_MFA_CODE = "mfa_code" diff --git a/homeassistant/components/monarchmoney/strings.json b/homeassistant/components/monarchmoney/strings.json index 071af39d5295bb..05c51e2ea6706c 100644 --- a/homeassistant/components/monarchmoney/strings.json +++ b/homeassistant/components/monarchmoney/strings.json @@ -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%]" } @@ -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%]" diff --git a/tests/components/monarchmoney/conftest.py b/tests/components/monarchmoney/conftest.py index a6ac2367daaa45..47087e616ffcbc 100644 --- a/tests/components/monarchmoney/conftest.py +++ b/tests/components/monarchmoney/conftest.py @@ -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": { diff --git a/tests/components/monarchmoney/test_config_flow.py b/tests/components/monarchmoney/test_config_flow.py index 47b238d49c21a9..866c97851a37d6 100644 --- a/tests/components/monarchmoney/test_config_flow.py +++ b/tests/components/monarchmoney/test_config_flow.py @@ -1,22 +1,44 @@ """Test the Monarch Money config flow.""" -from unittest.mock import AsyncMock, patch +from unittest.mock import AsyncMock + +from monarchmoney import LoginFailedException, RequireMFAException from homeassistant import config_entries -from homeassistant.components.monarchmoney.config_flow import InvalidAuth -from homeassistant.components.monarchmoney.const import CONF_MFA_SECRET, DOMAIN -from homeassistant.const import ( - CONF_EMAIL, - CONF_HOST, - CONF_PASSWORD, - CONF_TOKEN, - CONF_USERNAME, -) +from homeassistant.components.monarchmoney.const import CONF_MFA_CODE, DOMAIN +from homeassistant.const import CONF_EMAIL, CONF_PASSWORD, CONF_TOKEN from homeassistant.core import HomeAssistant from homeassistant.data_entry_flow import FlowResultType -async def test_form( +async def test_form_simple( + hass: HomeAssistant, mock_setup_entry: AsyncMock, mock_config_api: AsyncMock +) -> None: + """Test we get the form.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + assert result["type"] == FlowResultType.FORM + assert result["errors"] == {} + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + { + CONF_EMAIL: "test-username", + CONF_PASSWORD: "test-password", + }, + ) + await hass.async_block_till_done() + + assert result["type"] == FlowResultType.CREATE_ENTRY + assert result["title"] == "Monarch Money" + assert result["data"] == { + CONF_TOKEN: "mocked_token", + } + assert len(mock_setup_entry.mock_calls) == 1 + + +async def test_form_invalid_auth( hass: HomeAssistant, mock_setup_entry: AsyncMock, mock_config_api: AsyncMock ) -> None: """Test we get the form.""" @@ -26,12 +48,73 @@ async def test_form( assert result["type"] == FlowResultType.FORM assert result["errors"] == {} + # Change the login mock to raise an MFA required error + mock_config_api.return_value.login.side_effect = LoginFailedException( + "Invalid Auth" + ) + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + { + CONF_EMAIL: "test-username", + CONF_PASSWORD: "test-password", + }, + ) + await hass.async_block_till_done() + + assert result["type"] == FlowResultType.FORM + assert result["errors"] == {"base": "invalid_auth"} + + mock_config_api.return_value.login.side_effect = None result = await hass.config_entries.flow.async_configure( result["flow_id"], { CONF_EMAIL: "test-username", CONF_PASSWORD: "test-password", - CONF_MFA_SECRET: "test-mfa", + }, + ) + await hass.async_block_till_done() + + assert result["type"] == FlowResultType.CREATE_ENTRY + assert result["title"] == "Monarch Money" + assert result["data"] == { + CONF_TOKEN: "mocked_token", + } + assert len(mock_setup_entry.mock_calls) == 1 + + +async def test_form_mfa( + hass: HomeAssistant, mock_setup_entry: AsyncMock, mock_config_api: AsyncMock +) -> None: + """Test we get the form.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + assert result["type"] == FlowResultType.FORM + assert result["errors"] == {} + + # Change the login mock to raise an MFA required error + mock_config_api.return_value.login.side_effect = RequireMFAException("mfa_required") + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + { + CONF_EMAIL: "test-username", + CONF_PASSWORD: "test-password", + }, + ) + await hass.async_block_till_done() + + assert result["type"] == FlowResultType.FORM + assert result["errors"] == {"base": "mfa_required"} + assert result["step_id"] == "user" + + # Clear the mock now + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + { + CONF_MFA_CODE: "123456", }, ) await hass.async_block_till_done() @@ -73,57 +156,57 @@ async def test_form( # # } # assert len(mock_setup_entry.mock_calls) == 1 - - -async def test_form_invalid_auth( - hass: HomeAssistant, mock_setup_entry: AsyncMock -) -> None: - """Test we handle invalid auth.""" - result = await hass.config_entries.flow.async_init( - DOMAIN, context={"source": config_entries.SOURCE_USER} - ) - - with patch( - "homeassistant.components.monarchmoney.config_flow.PlaceholderHub.authenticate", - side_effect=InvalidAuth, - ): - result = await hass.config_entries.flow.async_configure( - result["flow_id"], - { - CONF_HOST: "1.1.1.1", - CONF_USERNAME: "test-username", - CONF_PASSWORD: "test-password", - }, - ) - - assert result["type"] == FlowResultType.FORM - assert result["errors"] == {"base": "invalid_auth"} - - # Make sure the config flow tests finish with either an - # FlowResultType.CREATE_ENTRY or FlowResultType.ABORT so - # we can show the config flow is able to recover from an error. - with patch( - "homeassistant.components.monarchmoney.config_flow.PlaceholderHub.authenticate", - return_value=True, - ): - result = await hass.config_entries.flow.async_configure( - result["flow_id"], - { - CONF_HOST: "1.1.1.1", - CONF_USERNAME: "test-username", - CONF_PASSWORD: "test-password", - }, - ) - await hass.async_block_till_done() - - assert result["type"] == FlowResultType.CREATE_ENTRY - assert result["title"] == "Name of the device" - assert result["data"] == { - CONF_HOST: "1.1.1.1", - CONF_USERNAME: "test-username", - CONF_PASSWORD: "test-password", - } - assert len(mock_setup_entry.mock_calls) == 1 +# +# +# async def test_form_invalid_auth( +# hass: HomeAssistant, mock_setup_entry: AsyncMock +# ) -> None: +# """Test we handle invalid auth.""" +# result = await hass.config_entries.flow.async_init( +# DOMAIN, context={"source": config_entries.SOURCE_USER} +# ) +# +# with patch( +# "homeassistant.components.monarchmoney.config_flow.PlaceholderHub.authenticate", +# side_effect=InvalidAuth, +# ): +# result = await hass.config_entries.flow.async_configure( +# result["flow_id"], +# { +# CONF_HOST: "1.1.1.1", +# CONF_USERNAME: "test-username", +# CONF_PASSWORD: "test-password", +# }, +# ) +# +# assert result["type"] == FlowResultType.FORM +# assert result["errors"] == {"base": "invalid_auth"} +# +# # Make sure the config flow tests finish with either an +# # FlowResultType.CREATE_ENTRY or FlowResultType.ABORT so +# # we can show the config flow is able to recover from an error. +# with patch( +# "homeassistant.components.monarchmoney.config_flow.PlaceholderHub.authenticate", +# return_value=True, +# ): +# result = await hass.config_entries.flow.async_configure( +# result["flow_id"], +# { +# CONF_HOST: "1.1.1.1", +# CONF_USERNAME: "test-username", +# CONF_PASSWORD: "test-password", +# }, +# ) +# await hass.async_block_till_done() +# +# assert result["type"] == FlowResultType.CREATE_ENTRY +# assert result["title"] == "Name of the device" +# assert result["data"] == { +# CONF_HOST: "1.1.1.1", +# CONF_USERNAME: "test-username", +# CONF_PASSWORD: "test-password", +# } +# assert len(mock_setup_entry.mock_calls) == 1 # async def test_form_cannot_connect( From 170a0770e5d22cdd855a2449950706296273816b Mon Sep 17 00:00:00 2001 From: Jeff Stein Date: Fri, 16 Aug 2024 09:50:44 -0600 Subject: [PATCH 04/67] Should be just about ready for PR --- .../components/monarchmoney/config_flow.py | 67 ++++---- .../components/monarchmoney/sensor.py | 2 +- .../components/monarchmoney/strings.json | 5 +- .../monarchmoney/test_config_flow.py | 150 ++---------------- tests/components/monarchmoney/test_sensor.py | 28 ---- 5 files changed, 50 insertions(+), 202 deletions(-) diff --git a/homeassistant/components/monarchmoney/config_flow.py b/homeassistant/components/monarchmoney/config_flow.py index e2cd41ce266457..7a05eba0cd993b 100644 --- a/homeassistant/components/monarchmoney/config_flow.py +++ b/homeassistant/components/monarchmoney/config_flow.py @@ -13,6 +13,11 @@ from homeassistant.const import CONF_EMAIL, CONF_PASSWORD, CONF_TOKEN from homeassistant.core import HomeAssistant from homeassistant.exceptions import HomeAssistantError +from homeassistant.helpers.selector import ( + TextSelector, + TextSelectorConfig, + TextSelectorType, +) from .const import CONF_MFA_CODE, DOMAIN, LOGGER @@ -21,8 +26,16 @@ STEP_USER_DATA_SCHEMA = vol.Schema( { - vol.Required(CONF_EMAIL): str, - vol.Required(CONF_PASSWORD): str, + vol.Required(CONF_EMAIL): TextSelector( + TextSelectorConfig( + type=TextSelectorType.EMAIL, + ), + ), + vol.Required(CONF_PASSWORD): TextSelector( + TextSelectorConfig( + type=TextSelectorType.PASSWORD, + ), + ), } ) @@ -43,7 +56,7 @@ async def validate_login( 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, "") + if not email: email = data[CONF_EMAIL] if not password: @@ -52,11 +65,15 @@ async def validate_login( if CONF_MFA_CODE in data: mfa_code = data[CONF_MFA_CODE] try: + LOGGER.debug("Attempting to authenticate with MFA code") await monarch_client.multi_factor_authenticate(email, password, mfa_code) - except LoginFailedException as err: - raise InvalidAuth from err + except KeyError: + # A bug in the backing lib that I don't control throws a KeyError if the MFA code is wrong + LOGGER.debug("Bad MFA Code") + raise BadMFA from None else: try: + LOGGER.debug("Attempting to authenticate") await monarch_client.login( email=email, password=password, @@ -68,10 +85,7 @@ async def validate_login( except LoginFailedException as err: raise InvalidAuth from err - # monarch_client.token LOGGER.debug(f"Connection successful - saving session to file {SESSION_FILE}") - - # Return info that you want to store in the config entry. return {"title": "Monarch Money", CONF_TOKEN: monarch_client.token} @@ -105,11 +119,14 @@ async def async_step_user( data_schema=STEP_MFA_DATA_SCHEMA, errors={"base": "mfa_required"}, ) + except BadMFA: + return self.async_show_form( + step_id="user", + data_schema=STEP_MFA_DATA_SCHEMA, + errors={"base": "bad_mfa"}, + ) 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]} @@ -120,29 +137,9 @@ 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.""" + + +class BadMFA(HomeAssistantError): + """Error to indicate the MFA code was bad.""" diff --git a/homeassistant/components/monarchmoney/sensor.py b/homeassistant/components/monarchmoney/sensor.py index 2243549cc73768..12d86125bc2511 100644 --- a/homeassistant/components/monarchmoney/sensor.py +++ b/homeassistant/components/monarchmoney/sensor.py @@ -83,7 +83,7 @@ class MonarchMoneySensorEntityDescription(SensorEntityDescription): device_class=SensorDeviceClass.MONETARY, value_fn=lambda account: account["currentBalance"], picture_fn=lambda account: account["logoUrl"], - icon_fn=lambda account: _type_to_icon(account), + icon_fn=_type_to_icon, ), MonarchMoneySensorEntityDescription( key="age", diff --git a/homeassistant/components/monarchmoney/strings.json b/homeassistant/components/monarchmoney/strings.json index 05c51e2ea6706c..b702156b186cc6 100644 --- a/homeassistant/components/monarchmoney/strings.json +++ b/homeassistant/components/monarchmoney/strings.json @@ -12,10 +12,9 @@ } }, "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%]", - "mfa_required": "Multi-factor authentication required." + "mfa_required": "Multi-factor authentication required.", + "bad_mfa": "Your code was invalid, please try again or use a recovery token." }, "abort": { "already_configured": "[%key:common::config_flow::abort::already_configured_device%]" diff --git a/tests/components/monarchmoney/test_config_flow.py b/tests/components/monarchmoney/test_config_flow.py index 866c97851a37d6..01166a2293b04a 100644 --- a/tests/components/monarchmoney/test_config_flow.py +++ b/tests/components/monarchmoney/test_config_flow.py @@ -109,8 +109,22 @@ async def test_form_mfa( assert result["errors"] == {"base": "mfa_required"} assert result["step_id"] == "user" - # Clear the mock now + # Add a bad MFA Code response + mock_config_api.return_value.multi_factor_authenticate.side_effect = KeyError + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + { + CONF_MFA_CODE: "123456", + }, + ) + await hass.async_block_till_done() + assert result["type"] == FlowResultType.FORM + assert result["errors"] == {"base": "bad_mfa"} + assert result["step_id"] == "user" + + # Use a good MFA Code - Clear mock + mock_config_api.return_value.multi_factor_authenticate.side_effect = None result = await hass.config_entries.flow.async_configure( result["flow_id"], { @@ -125,137 +139,3 @@ async def test_form_mfa( CONF_TOKEN: "mocked_token", } assert len(mock_setup_entry.mock_calls) == 1 - - -# async def test_form(hass: HomeAssistant, mock_setup_entry: AsyncMock) -> None: -# """Test we get the form.""" -# result = await hass.config_entries.flow.async_init( -# DOMAIN, context={"source": config_entries.SOURCE_USER} -# ) -# assert result["type"] == FlowResultType.FORM -# assert result["errors"] == {} -# -# with patch( -# "homeassistant.components.monarchmoney.config_flow.MonarchMoney.login", -# return_value=True, -# ), patch("homeassistant.components.monarchmoney.config_flow.MonarchMoney.token", return_value="1111"): -# result = await hass.config_entries.flow.async_configure( -# result["flow_id"], -# { -# CONF_EMAIL: "test-username", -# CONF_PASSWORD: "test-password", -# CONF_MFA_SECRET: "test-mfa", -# }, -# ) -# await hass.async_block_till_done() -# -# assert result["type"] == FlowResultType.CREATE_ENTRY -# assert result["title"] == "Monarch Money" -# assert result["data"] == { -# CONF_TOKEN: "1.1.1.1", -# -# } -# assert len(mock_setup_entry.mock_calls) == 1 -# -# -# async def test_form_invalid_auth( -# hass: HomeAssistant, mock_setup_entry: AsyncMock -# ) -> None: -# """Test we handle invalid auth.""" -# result = await hass.config_entries.flow.async_init( -# DOMAIN, context={"source": config_entries.SOURCE_USER} -# ) -# -# with patch( -# "homeassistant.components.monarchmoney.config_flow.PlaceholderHub.authenticate", -# side_effect=InvalidAuth, -# ): -# result = await hass.config_entries.flow.async_configure( -# result["flow_id"], -# { -# CONF_HOST: "1.1.1.1", -# CONF_USERNAME: "test-username", -# CONF_PASSWORD: "test-password", -# }, -# ) -# -# assert result["type"] == FlowResultType.FORM -# assert result["errors"] == {"base": "invalid_auth"} -# -# # Make sure the config flow tests finish with either an -# # FlowResultType.CREATE_ENTRY or FlowResultType.ABORT so -# # we can show the config flow is able to recover from an error. -# with patch( -# "homeassistant.components.monarchmoney.config_flow.PlaceholderHub.authenticate", -# return_value=True, -# ): -# result = await hass.config_entries.flow.async_configure( -# result["flow_id"], -# { -# CONF_HOST: "1.1.1.1", -# CONF_USERNAME: "test-username", -# CONF_PASSWORD: "test-password", -# }, -# ) -# await hass.async_block_till_done() -# -# assert result["type"] == FlowResultType.CREATE_ENTRY -# assert result["title"] == "Name of the device" -# assert result["data"] == { -# CONF_HOST: "1.1.1.1", -# CONF_USERNAME: "test-username", -# CONF_PASSWORD: "test-password", -# } -# assert len(mock_setup_entry.mock_calls) == 1 - - -# async def test_form_cannot_connect( -# hass: HomeAssistant, mock_setup_entry: AsyncMock -# ) -> None: -# """Test we handle cannot connect error.""" -# result = await hass.config_entries.flow.async_init( -# DOMAIN, context={"source": config_entries.SOURCE_USER} -# ) -# -# with patch( -# "homeassistant.components.monarchmoney.config_flow.PlaceholderHub.authenticate", -# side_effect=CannotConnect, -# ): -# result = await hass.config_entries.flow.async_configure( -# result["flow_id"], -# { -# CONF_HOST: "1.1.1.1", -# CONF_USERNAME: "test-username", -# CONF_PASSWORD: "test-password", -# }, -# ) -# -# assert result["type"] == FlowResultType.FORM -# assert result["errors"] == {"base": "cannot_connect"} -# -# # Make sure the config flow tests finish with either an -# # FlowResultType.CREATE_ENTRY or FlowResultType.ABORT so -# # we can show the config flow is able to recover from an error. -# -# with patch( -# "homeassistant.components.monarchmoney.config_flow.PlaceholderHub.authenticate", -# return_value=True, -# ): -# result = await hass.config_entries.flow.async_configure( -# result["flow_id"], -# { -# CONF_HOST: "1.1.1.1", -# CONF_USERNAME: "test-username", -# CONF_PASSWORD: "test-password", -# }, -# ) -# await hass.async_block_till_done() -# -# assert result["type"] == FlowResultType.CREATE_ENTRY -# assert result["title"] == "Name of the device" -# assert result["data"] == { -# CONF_HOST: "1.1.1.1", -# CONF_USERNAME: "test-username", -# CONF_PASSWORD: "test-password", -# } -# assert len(mock_setup_entry.mock_calls) == 1 diff --git a/tests/components/monarchmoney/test_sensor.py b/tests/components/monarchmoney/test_sensor.py index 77e67ed1de69c7..2e513d6228abff 100644 --- a/tests/components/monarchmoney/test_sensor.py +++ b/tests/components/monarchmoney/test_sensor.py @@ -25,31 +25,3 @@ async def test_all_entities( await setup_integration(hass, mock_config_entry) await snapshot_platform(hass, entity_registry, snapshot, mock_config_entry.entry_id) - - -# @pytest.mark.asyncio -# async def test_login(mock_api): -# """Test the login method of the MonarchMoney class.""" -# # Arrange -# monarch_client = mock_api -# -# # Act -# try: -# await monarch_client.login( -# email="test@example.com", -# password="password", -# save_session=False, -# use_saved_session=False, -# mfa_secret_key="mfa_secret_key", -# ) -# except LoginFailedException as exc: -# raise LoginFailedException from exc -# -# # Assert -# monarch_client.login.assert_awaited_once_with( -# email="test@example.com", -# password="password", -# save_session=False, -# use_saved_session=False, -# mfa_secret_key="mfa_secret_key", -# ) From 0b7e7048977e377b3e630d41a208d1d95d7b0aae Mon Sep 17 00:00:00 2001 From: Jeff Stein Date: Fri, 16 Aug 2024 11:06:44 -0600 Subject: [PATCH 05/67] Adding some error handling for wonky acocunts --- .../components/monarchmoney/entity.py | 7 +- .../monarchmoney/fixtures/get_accounts.json | 41 +++++ .../monarchmoney/snapshots/test_sensor.ambr | 148 ++++++++++++++++++ 3 files changed, 194 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/monarchmoney/entity.py b/homeassistant/components/monarchmoney/entity.py index 5c7b9c3bb58246..2f96aa29a13eea 100644 --- a/homeassistant/components/monarchmoney/entity.py +++ b/homeassistant/components/monarchmoney/entity.py @@ -28,7 +28,11 @@ def __init__( self._account_id = account["id"] # Parse out some fields - institution = account["institution"]["name"] + institution = "Manual entry" + configuration_url = "http://monarchmoney.com" + if account.get("institution") is not None: + institution = account["institution"].get("name", "Manual entry") + configuration_url = account["institution"]["url"] provider = account.get("dataProvider", "Manual input") if account.get("credential") is not None: @@ -36,7 +40,6 @@ def __init__( self._attr_attribution = f"Data provided by Monarch Money API via {provider}" - configuration_url = account["institution"]["url"] if not configuration_url.startswith(("http://", "https://")): configuration_url = f"http://{configuration_url}" diff --git a/tests/components/monarchmoney/fixtures/get_accounts.json b/tests/components/monarchmoney/fixtures/get_accounts.json index ac28c3f672ef26..81a8902cfb1899 100644 --- a/tests/components/monarchmoney/fixtures/get_accounts.json +++ b/tests/components/monarchmoney/fixtures/get_accounts.json @@ -419,6 +419,47 @@ "__typename": "Institution" }, "__typename": "Account" + }, + + { + "id": "186321412999033223", + "displayName": "Wallet", + "syncDisabled": false, + "deactivatedAt": null, + "isHidden": false, + "isAsset": true, + "mask": null, + "createdAt": "2024-08-16T14:22:10.440514+00:00", + "updatedAt": "2024-08-16T14:22:10.512731+00:00", + "displayLastUpdatedAt": "2024-08-16T14:22:10.512731+00:00", + "currentBalance": 20.0, + "displayBalance": 20.0, + "includeInNetWorth": true, + "hideFromList": false, + "hideTransactionsFromReports": false, + "includeBalanceInNetWorth": true, + "includeInGoalBalance": true, + "dataProvider": "", + "dataProviderAccountId": null, + "isManual": true, + "transactionsCount": 0, + "holdingsCount": 0, + "manualInvestmentsTrackingMethod": null, + "order": 14, + "logoUrl": null, + "type": { + "name": "depository", + "display": "Cash", + "__typename": "AccountType" + }, + "subtype": { + "name": "prepaid", + "display": "Prepaid", + "__typename": "AccountSubtype" + }, + "credential": null, + "institution": null, + "__typename": "Account" } ], "householdPreferences": { diff --git a/tests/components/monarchmoney/snapshots/test_sensor.ambr b/tests/components/monarchmoney/snapshots/test_sensor.ambr index 4dc9a9769e678e..bb787f1c92a466 100644 --- a/tests/components/monarchmoney/snapshots/test_sensor.ambr +++ b/tests/components/monarchmoney/snapshots/test_sensor.ambr @@ -1,4 +1,152 @@ # serializer version: 1 +# name: test_all_entities[sensor.manual_entry_wallet_balance-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.manual_entry_wallet_balance', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': 'mdi:cash', + 'original_name': 'Balance', + 'platform': 'monarchmoney', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'balance', + 'unique_id': 'Manual entry_Wallet_balance', + 'unit_of_measurement': None, + }) +# --- +# name: test_all_entities[sensor.manual_entry_wallet_balance-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'attribution': 'Data provided by Monarch Money API via ', + 'device_class': 'monetary', + 'friendly_name': 'Manual entry Wallet Balance', + 'icon': 'mdi:cash', + 'state_class': , + }), + 'context': , + 'entity_id': 'sensor.manual_entry_wallet_balance', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '20.0', + }) +# --- +# name: test_all_entities[sensor.manual_entry_wallet_data_age-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': , + 'entity_id': 'sensor.manual_entry_wallet_data_age', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Data age', + 'platform': 'monarchmoney', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'age', + 'unique_id': 'Manual entry_Wallet_age', + 'unit_of_measurement': None, + }) +# --- +# name: test_all_entities[sensor.manual_entry_wallet_data_age-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'attribution': 'Data provided by Monarch Money API via ', + 'device_class': 'timestamp', + 'friendly_name': 'Manual entry Wallet Data age', + }), + 'context': , + 'entity_id': 'sensor.manual_entry_wallet_data_age', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '2024-08-16T14:22:10+00:00', + }) +# --- +# name: test_all_entities[sensor.manual_entry_wallet_first_sync-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': , + 'entity_id': 'sensor.manual_entry_wallet_first_sync', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'First sync', + 'platform': 'monarchmoney', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'created', + 'unique_id': 'Manual entry_Wallet_created', + 'unit_of_measurement': None, + }) +# --- +# name: test_all_entities[sensor.manual_entry_wallet_first_sync-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'attribution': 'Data provided by Monarch Money API via ', + 'device_class': 'timestamp', + 'friendly_name': 'Manual entry Wallet First sync', + }), + 'context': , + 'entity_id': 'sensor.manual_entry_wallet_first_sync', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '2024-08-16T14:22:10+00:00', + }) +# --- # name: test_all_entities[sensor.rando_bank_checking_balance-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ From a9fa0643f185e032acda270e13962ee5bf7394f3 Mon Sep 17 00:00:00 2001 From: Jeff Stein Date: Fri, 16 Aug 2024 11:24:49 -0600 Subject: [PATCH 06/67] Adding USD hardcoded as this is all that is currently supported i believe --- homeassistant/components/monarchmoney/sensor.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/monarchmoney/sensor.py b/homeassistant/components/monarchmoney/sensor.py index 12d86125bc2511..3305b32e9c68d6 100644 --- a/homeassistant/components/monarchmoney/sensor.py +++ b/homeassistant/components/monarchmoney/sensor.py @@ -11,7 +11,7 @@ SensorEntityDescription, SensorStateClass, ) -from homeassistant.const import EntityCategory +from homeassistant.const import CURRENCY_DOLLAR, EntityCategory from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.typing import StateType @@ -84,6 +84,7 @@ class MonarchMoneySensorEntityDescription(SensorEntityDescription): value_fn=lambda account: account["currentBalance"], picture_fn=lambda account: account["logoUrl"], icon_fn=_type_to_icon, + native_unit_of_measurement=CURRENCY_DOLLAR, ), MonarchMoneySensorEntityDescription( key="age", From 6d97ed09dd49eaf58a83b915ebfbaaa052ed48d6 Mon Sep 17 00:00:00 2001 From: Jeff Stein Date: Fri, 16 Aug 2024 11:25:12 -0600 Subject: [PATCH 07/67] updating snapshots --- .../monarchmoney/snapshots/test_sensor.ambr | 24 ++++++++++++------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/tests/components/monarchmoney/snapshots/test_sensor.ambr b/tests/components/monarchmoney/snapshots/test_sensor.ambr index bb787f1c92a466..e9654695edf685 100644 --- a/tests/components/monarchmoney/snapshots/test_sensor.ambr +++ b/tests/components/monarchmoney/snapshots/test_sensor.ambr @@ -31,7 +31,7 @@ 'supported_features': 0, 'translation_key': 'balance', 'unique_id': 'Manual entry_Wallet_balance', - 'unit_of_measurement': None, + 'unit_of_measurement': '$', }) # --- # name: test_all_entities[sensor.manual_entry_wallet_balance-state] @@ -42,6 +42,7 @@ 'friendly_name': 'Manual entry Wallet Balance', 'icon': 'mdi:cash', 'state_class': , + 'unit_of_measurement': '$', }), 'context': , 'entity_id': 'sensor.manual_entry_wallet_balance', @@ -179,7 +180,7 @@ 'supported_features': 0, 'translation_key': 'balance', 'unique_id': 'Rando Bank_Checking_balance', - 'unit_of_measurement': None, + 'unit_of_measurement': '$', }) # --- # name: test_all_entities[sensor.rando_bank_checking_balance-state] @@ -191,6 +192,7 @@ 'friendly_name': 'Rando Bank Checking Balance', 'icon': 'mdi:checkbook', 'state_class': , + 'unit_of_measurement': '$', }), 'context': , 'entity_id': 'sensor.rando_bank_checking_balance', @@ -328,7 +330,7 @@ 'supported_features': 0, 'translation_key': 'balance', 'unique_id': 'Rando Brokerage_Brokerage_balance', - 'unit_of_measurement': None, + 'unit_of_measurement': '$', }) # --- # name: test_all_entities[sensor.rando_brokerage_brokerage_balance-state] @@ -340,6 +342,7 @@ 'friendly_name': 'Rando Brokerage Brokerage Balance', 'icon': 'mdi:chart-line', 'state_class': , + 'unit_of_measurement': '$', }), 'context': , 'entity_id': 'sensor.rando_brokerage_brokerage_balance', @@ -477,7 +480,7 @@ 'supported_features': 0, 'translation_key': 'balance', 'unique_id': 'Rando Credit_Credit Card_balance', - 'unit_of_measurement': None, + 'unit_of_measurement': '$', }) # --- # name: test_all_entities[sensor.rando_credit_credit_card_balance-state] @@ -489,6 +492,7 @@ 'friendly_name': 'Rando Credit Credit Card Balance', 'icon': 'mdi:credit-card-outline', 'state_class': , + 'unit_of_measurement': '$', }), 'context': , 'entity_id': 'sensor.rando_credit_credit_card_balance', @@ -626,7 +630,7 @@ 'supported_features': 0, 'translation_key': 'balance', 'unique_id': 'Rando Employer Investments_401.k_balance', - 'unit_of_measurement': None, + 'unit_of_measurement': '$', }) # --- # name: test_all_entities[sensor.rando_employer_investments_401_k_balance-state] @@ -638,6 +642,7 @@ 'friendly_name': 'Rando Employer Investments 401.k Balance', 'icon': 'mdi:chart-line', 'state_class': , + 'unit_of_measurement': '$', }), 'context': , 'entity_id': 'sensor.rando_employer_investments_401_k_balance', @@ -775,7 +780,7 @@ 'supported_features': 0, 'translation_key': 'balance', 'unique_id': 'Rando Investments_Roth IRA_balance', - 'unit_of_measurement': None, + 'unit_of_measurement': '$', }) # --- # name: test_all_entities[sensor.rando_investments_roth_ira_balance-state] @@ -787,6 +792,7 @@ 'friendly_name': 'Rando Investments Roth IRA Balance', 'icon': 'mdi:chart-line', 'state_class': , + 'unit_of_measurement': '$', }), 'context': , 'entity_id': 'sensor.rando_investments_roth_ira_balance', @@ -924,7 +930,7 @@ 'supported_features': 0, 'translation_key': 'balance', 'unique_id': 'Rando Mortgage_Mortgage_balance', - 'unit_of_measurement': None, + 'unit_of_measurement': '$', }) # --- # name: test_all_entities[sensor.rando_mortgage_mortgage_balance-state] @@ -936,6 +942,7 @@ 'friendly_name': 'Rando Mortgage Mortgage Balance', 'icon': 'mdi:home-city-outline', 'state_class': , + 'unit_of_measurement': '$', }), 'context': , 'entity_id': 'sensor.rando_mortgage_mortgage_balance', @@ -1073,7 +1080,7 @@ 'supported_features': 0, 'translation_key': 'balance', 'unique_id': 'Zillow_House_balance', - 'unit_of_measurement': None, + 'unit_of_measurement': '$', }) # --- # name: test_all_entities[sensor.zillow_house_balance-state] @@ -1085,6 +1092,7 @@ 'friendly_name': 'Zillow House Balance', 'icon': 'mdi:cash', 'state_class': , + 'unit_of_measurement': '$', }), 'context': , 'entity_id': 'sensor.zillow_house_balance', From 63fb8c48a6e00252bbf63c505d74a351b4bc4226 Mon Sep 17 00:00:00 2001 From: Jeff Stein Date: Fri, 16 Aug 2024 12:32:15 -0600 Subject: [PATCH 08/67] updating entity descrition a little --- .../components/monarchmoney/entity.py | 6 +- .../components/monarchmoney/sensor.py | 20 ++- .../monarchmoney/fixtures/get_accounts.json | 48 +++++- .../monarchmoney/snapshots/test_sensor.ambr | 150 ++++++++++++++++++ 4 files changed, 217 insertions(+), 7 deletions(-) diff --git a/homeassistant/components/monarchmoney/entity.py b/homeassistant/components/monarchmoney/entity.py index 2f96aa29a13eea..b91f484ac26dae 100644 --- a/homeassistant/components/monarchmoney/entity.py +++ b/homeassistant/components/monarchmoney/entity.py @@ -47,13 +47,17 @@ def __init__( f"{institution}_{account["displayName"]}_{description.translation_key}" ) + atype = account["type"]["display"] + asubtype = account["subtype"]["display"] + self._attr_device_info = DeviceInfo( identifiers={(DOMAIN, account["id"])}, name=f"{institution} {account["displayName"]}", entry_type=DeviceEntryType.SERVICE, manufacturer=provider, - model=institution, + model=f"{institution} - {atype} - {asubtype}", configuration_url=configuration_url, + suggested_area="Banking/Finance", ) @property diff --git a/homeassistant/components/monarchmoney/sensor.py b/homeassistant/components/monarchmoney/sensor.py index 3305b32e9c68d6..1e61da14cd521a 100644 --- a/homeassistant/components/monarchmoney/sensor.py +++ b/homeassistant/components/monarchmoney/sensor.py @@ -35,7 +35,15 @@ def _type_to_icon(account: Any) -> str: "st_403b": "mdi:chart-bell-curve-cumulative", "st_529": "mdi:school-outline", }, - "credit": {"credit_card": "mdi:credit-card-outline"}, + "vehicle": { + "car": "mdi:car", + "boat": "mdi:sail-boat", + "motorcycle": "mdi:motorbike", + "snowmobile": "mdi:snowmobile", + "bicycle": "mdi:bicycle", + "other": "mdi:car", + }, + "credit": {"credit_card": "mdi:credit-card"}, "depository": { "cash_management": "mdi:cash", "checking": "mdi:checkbook", @@ -43,7 +51,7 @@ def _type_to_icon(account: Any) -> str: "money_market": "mdi:piggy-bank-outline", }, "loan": { - "line_of_credit": "mdi:credit-card-plus-outline", + "line_of_credit": "mdi:credit-card-plus", "loan": "mdi:bank-outline", "mortgage": "mdi:home-city-outline", }, @@ -51,9 +59,10 @@ def _type_to_icon(account: Any) -> str: default_icons = { "brokerage": "mdi:chart-line", - "credit": "mdi:credit-card-outline", + "credit": "mdi:credit-card", "depository": "mdi:cash", "loan": "mdi:bank-outline", + "vehicle": "mdi:car", } if account_subtype not in icon_mapping.get(account_type, {}): LOGGER.info( @@ -61,8 +70,9 @@ def _type_to_icon(account: Any) -> str: ) return default_icons.get(account_type, "mdi:cash") - return icon_mapping.get(account_type, {}).get( - account_subtype, default_icons.get(account_type, "mdi:cash") + account_type_icons = icon_mapping.get(account_type, {}) + return account_type_icons.get( + account_subtype, default_icons.get(account_type, "mdi:help") ) diff --git a/tests/components/monarchmoney/fixtures/get_accounts.json b/tests/components/monarchmoney/fixtures/get_accounts.json index 81a8902cfb1899..ddaecc1721bea1 100644 --- a/tests/components/monarchmoney/fixtures/get_accounts.json +++ b/tests/components/monarchmoney/fixtures/get_accounts.json @@ -124,6 +124,53 @@ }, "__typename": "Account" }, + + { + "id": "121212192626186051", + "displayName": "2050 Toyota RAV8", + "syncDisabled": false, + "deactivatedAt": null, + "isHidden": false, + "isAsset": true, + "mask": null, + "createdAt": "2024-08-16T17:37:21.885036+00:00", + "updatedAt": "2024-08-16T17:37:21.885057+00:00", + "displayLastUpdatedAt": "2024-08-16T17:37:21.885057+00:00", + "currentBalance": 11075.58, + "displayBalance": 11075.58, + "includeInNetWorth": true, + "hideFromList": false, + "hideTransactionsFromReports": false, + "includeBalanceInNetWorth": true, + "includeInGoalBalance": false, + "dataProvider": "vin_audit", + "dataProviderAccountId": "1111111v5cw252004", + "isManual": false, + "transactionsCount": 0, + "holdingsCount": 0, + "manualInvestmentsTrackingMethod": null, + "order": 0, + "logoUrl": "https://api.monarchmoney.com/cdn-cgi/image/width=128/images/institution/159427559853802644", + "type": { + "name": "vehicle", + "display": "Vehicles", + "__typename": "AccountType" + }, + "subtype": { + "name": "car", + "display": "Car", + "__typename": "AccountSubtype" + }, + "credential": null, + "institution": { + "id": "123456789853802644", + "name": "VinAudit", + "primaryColor": "#74ab16", + "url": "https://www.vinaudit.com/", + "__typename": "Institution" + }, + "__typename": "Account" + }, { "id": "9000000007", "displayName": "Credit Card", @@ -420,7 +467,6 @@ }, "__typename": "Account" }, - { "id": "186321412999033223", "displayName": "Wallet", diff --git a/tests/components/monarchmoney/snapshots/test_sensor.ambr b/tests/components/monarchmoney/snapshots/test_sensor.ambr index e9654695edf685..8e4c06b9e74ad3 100644 --- a/tests/components/monarchmoney/snapshots/test_sensor.ambr +++ b/tests/components/monarchmoney/snapshots/test_sensor.ambr @@ -1048,6 +1048,156 @@ 'state': '2021-10-15T01:45:25+00:00', }) # --- +# name: test_all_entities[sensor.vinaudit_2050_toyota_rav8_balance-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.vinaudit_2050_toyota_rav8_balance', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': 'mdi:cash', + 'original_name': 'Balance', + 'platform': 'monarchmoney', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'balance', + 'unique_id': 'VinAudit_2050 Toyota RAV8_balance', + 'unit_of_measurement': '$', + }) +# --- +# name: test_all_entities[sensor.vinaudit_2050_toyota_rav8_balance-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'attribution': 'Data provided by Monarch Money API via vin_audit', + 'device_class': 'monetary', + 'entity_picture': 'https://api.monarchmoney.com/cdn-cgi/image/width=128/images/institution/159427559853802644', + 'friendly_name': 'VinAudit 2050 Toyota RAV8 Balance', + 'icon': 'mdi:cash', + 'state_class': , + 'unit_of_measurement': '$', + }), + 'context': , + 'entity_id': 'sensor.vinaudit_2050_toyota_rav8_balance', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '11075.58', + }) +# --- +# name: test_all_entities[sensor.vinaudit_2050_toyota_rav8_data_age-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': , + 'entity_id': 'sensor.vinaudit_2050_toyota_rav8_data_age', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Data age', + 'platform': 'monarchmoney', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'age', + 'unique_id': 'VinAudit_2050 Toyota RAV8_age', + 'unit_of_measurement': None, + }) +# --- +# name: test_all_entities[sensor.vinaudit_2050_toyota_rav8_data_age-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'attribution': 'Data provided by Monarch Money API via vin_audit', + 'device_class': 'timestamp', + 'friendly_name': 'VinAudit 2050 Toyota RAV8 Data age', + }), + 'context': , + 'entity_id': 'sensor.vinaudit_2050_toyota_rav8_data_age', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '2024-08-16T17:37:21+00:00', + }) +# --- +# name: test_all_entities[sensor.vinaudit_2050_toyota_rav8_first_sync-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': , + 'entity_id': 'sensor.vinaudit_2050_toyota_rav8_first_sync', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'First sync', + 'platform': 'monarchmoney', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'created', + 'unique_id': 'VinAudit_2050 Toyota RAV8_created', + 'unit_of_measurement': None, + }) +# --- +# name: test_all_entities[sensor.vinaudit_2050_toyota_rav8_first_sync-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'attribution': 'Data provided by Monarch Money API via vin_audit', + 'device_class': 'timestamp', + 'friendly_name': 'VinAudit 2050 Toyota RAV8 First sync', + }), + 'context': , + 'entity_id': 'sensor.vinaudit_2050_toyota_rav8_first_sync', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '2024-08-16T17:37:21+00:00', + }) +# --- # name: test_all_entities[sensor.zillow_house_balance-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ From 7bca93fb6df9636b0f82813162548f86bd59578b Mon Sep 17 00:00:00 2001 From: Jeff Stein Date: Fri, 16 Aug 2024 14:42:26 -0600 Subject: [PATCH 09/67] Addign cashflow in --- homeassistant/components/monarchmoney/coordinator.py | 8 +++++--- tests/components/monarchmoney/snapshots/test_sensor.ambr | 8 ++++---- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/homeassistant/components/monarchmoney/coordinator.py b/homeassistant/components/monarchmoney/coordinator.py index 438ba2cf1354fa..43d2b6dca0143b 100644 --- a/homeassistant/components/monarchmoney/coordinator.py +++ b/homeassistant/components/monarchmoney/coordinator.py @@ -31,17 +31,19 @@ def __init__(self, hass, client): async def _async_update_data(self) -> Any: """Fetch data for all accounts.""" - return await self.client.get_accounts() + account_data = await self.client.get_accounts() + cashflow_summary = await self.client.get_cashflow_summary() + return {"account_data": account_data, "cashflow_summary": cashflow_summary} @property def accounts(self) -> Any: """Return accounts.""" - return self.data["accounts"] + return self.data["account_data"]["accounts"] def get_account_for_id(self, account_id: str) -> Any | None: """Get account for id.""" - for account in self.data["accounts"]: + for account in self.data["account_data"]["accounts"]: if account["id"] == account_id: return account return None diff --git a/tests/components/monarchmoney/snapshots/test_sensor.ambr b/tests/components/monarchmoney/snapshots/test_sensor.ambr index 8e4c06b9e74ad3..8435b09374bbb6 100644 --- a/tests/components/monarchmoney/snapshots/test_sensor.ambr +++ b/tests/components/monarchmoney/snapshots/test_sensor.ambr @@ -473,7 +473,7 @@ 'options': dict({ }), 'original_device_class': , - 'original_icon': 'mdi:credit-card-outline', + 'original_icon': 'mdi:credit-card', 'original_name': 'Balance', 'platform': 'monarchmoney', 'previous_unique_id': None, @@ -490,7 +490,7 @@ 'device_class': 'monetary', 'entity_picture': 'data:image/png;base64,base64Nonce', 'friendly_name': 'Rando Credit Credit Card Balance', - 'icon': 'mdi:credit-card-outline', + 'icon': 'mdi:credit-card', 'state_class': , 'unit_of_measurement': '$', }), @@ -1073,7 +1073,7 @@ 'options': dict({ }), 'original_device_class': , - 'original_icon': 'mdi:cash', + 'original_icon': 'mdi:car', 'original_name': 'Balance', 'platform': 'monarchmoney', 'previous_unique_id': None, @@ -1090,7 +1090,7 @@ 'device_class': 'monetary', 'entity_picture': 'https://api.monarchmoney.com/cdn-cgi/image/width=128/images/institution/159427559853802644', 'friendly_name': 'VinAudit 2050 Toyota RAV8 Balance', - 'icon': 'mdi:cash', + 'icon': 'mdi:car', 'state_class': , 'unit_of_measurement': '$', }), From 5a3d18da8e5fa016128a9d529780ea9e736f56ed Mon Sep 17 00:00:00 2001 From: Jeff Stein Date: Fri, 16 Aug 2024 15:15:40 -0600 Subject: [PATCH 10/67] adding aggregate sensors --- .../components/monarchmoney/coordinator.py | 11 +- .../components/monarchmoney/entity.py | 32 ++- .../components/monarchmoney/sensor.py | 60 +++++- .../components/monarchmoney/strings.json | 13 ++ tests/components/monarchmoney/conftest.py | 4 + .../fixtures/get_cashflow_summary.json | 14 ++ .../monarchmoney/snapshots/test_sensor.ambr | 203 ++++++++++++++++++ 7 files changed, 331 insertions(+), 6 deletions(-) create mode 100644 tests/components/monarchmoney/fixtures/get_cashflow_summary.json diff --git a/homeassistant/components/monarchmoney/coordinator.py b/homeassistant/components/monarchmoney/coordinator.py index 43d2b6dca0143b..9f7f22a6a1d032 100644 --- a/homeassistant/components/monarchmoney/coordinator.py +++ b/homeassistant/components/monarchmoney/coordinator.py @@ -33,12 +33,19 @@ async def _async_update_data(self) -> Any: account_data = await self.client.get_accounts() cashflow_summary = await self.client.get_cashflow_summary() - return {"account_data": account_data, "cashflow_summary": cashflow_summary} + return { + "account_data": account_data, + "cashflow_summary": cashflow_summary, + } + + @property + def cashflow_summary(self) -> Any: + """Return cashflow summary.""" + return self.data["cashflow_summary"]["summary"][0]["summary"] @property def accounts(self) -> Any: """Return accounts.""" - return self.data["account_data"]["accounts"] def get_account_for_id(self, account_id: str) -> Any | None: diff --git a/homeassistant/components/monarchmoney/entity.py b/homeassistant/components/monarchmoney/entity.py index b91f484ac26dae..40f4a1a45962dd 100644 --- a/homeassistant/components/monarchmoney/entity.py +++ b/homeassistant/components/monarchmoney/entity.py @@ -10,7 +10,36 @@ from .coordinator import MonarchMoneyDataUpdateCoordinator -class MonarchMoneyEntity(CoordinatorEntity[MonarchMoneyDataUpdateCoordinator]): +class MonarchMoneyBaseEntity(CoordinatorEntity[MonarchMoneyDataUpdateCoordinator]): + """Custom entity for Cashflow sensors.""" + + _attr_has_entity_name = True + + def __init__( + self, + coordinator: MonarchMoneyDataUpdateCoordinator, + description: EntityDescription, + data: Any, + ) -> None: + """Initialize the Monarch Money Entity.""" + super().__init__(coordinator) + self._attr_unique_id = f"monarch_money_cashflow_{description.key}" + + self._data = data + self.entity_description = description + self._attr_device_info = DeviceInfo( + identifiers={(DOMAIN, "monarch_money_cashflow")}, + name="Cashflow", + suggested_area="Banking/Finance", + ) + + @property + def summary_data(self) -> Any: + """Return cashflow summary data.""" + return self.coordinator.cashflow_summary + + +class MonarchMoneyAccountEntity(CoordinatorEntity[MonarchMoneyDataUpdateCoordinator]): """Define a generic class for Entities.""" _attr_has_entity_name = True @@ -25,6 +54,7 @@ def __init__( super().__init__(coordinator) self.entity_description = description + self._account_id = account["id"] # Parse out some fields diff --git a/homeassistant/components/monarchmoney/sensor.py b/homeassistant/components/monarchmoney/sensor.py index 1e61da14cd521a..aebc9e999c5db5 100644 --- a/homeassistant/components/monarchmoney/sensor.py +++ b/homeassistant/components/monarchmoney/sensor.py @@ -11,14 +11,14 @@ SensorEntityDescription, SensorStateClass, ) -from homeassistant.const import CURRENCY_DOLLAR, EntityCategory +from homeassistant.const import CURRENCY_DOLLAR, PERCENTAGE, EntityCategory from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.typing import StateType from . import MonarchMoneyConfigEntry from .const import LOGGER -from .entity import MonarchMoneyEntity +from .entity import MonarchMoneyAccountEntity, MonarchMoneyBaseEntity def _type_to_icon(account: Any) -> str: @@ -112,8 +112,53 @@ class MonarchMoneySensorEntityDescription(SensorEntityDescription): ), ) +MONARCH_CASHFLOW_SENSORS: tuple[MonarchMoneySensorEntityDescription, ...] = ( + MonarchMoneySensorEntityDescription( + key="sum_income", + translation_key="sum_income", + value_fn=lambda summary: summary["sumIncome"], + state_class=SensorStateClass.TOTAL, + device_class=SensorDeviceClass.MONETARY, + native_unit_of_measurement=CURRENCY_DOLLAR, + ), + MonarchMoneySensorEntityDescription( + key="sum_expense", + translation_key="sum_expense", + value_fn=lambda summary: summary["sumExpense"], + state_class=SensorStateClass.TOTAL, + device_class=SensorDeviceClass.MONETARY, + native_unit_of_measurement=CURRENCY_DOLLAR, + ), + MonarchMoneySensorEntityDescription( + key="savings", + translation_key="savings", + value_fn=lambda summary: summary["savings"], + state_class=SensorStateClass.TOTAL, + device_class=SensorDeviceClass.MONETARY, + native_unit_of_measurement=CURRENCY_DOLLAR, + ), + MonarchMoneySensorEntityDescription( + key="savings_rate", + translation_key="savings_rate", + value_fn=lambda summary: summary["savingsRate"], + suggested_display_precision=1, + native_unit_of_measurement=PERCENTAGE, + ), +) + + +class MonarchMoneyCashFlowSensor(MonarchMoneyBaseEntity, SensorEntity): + """Cashflow summary sensor.""" + + entity_description: MonarchMoneySensorEntityDescription + + @property + def native_value(self) -> StateType | datetime | None: + """Return the state.""" + return self.entity_description.value_fn(self.summary_data) + -class MonarchMoneySensor(MonarchMoneyEntity, SensorEntity): +class MonarchMoneySensor(MonarchMoneyAccountEntity, SensorEntity): """Define a monarch money sensor.""" entity_description: MonarchMoneySensorEntityDescription @@ -155,3 +200,12 @@ async def async_setup_entry( for account in mm_coordinator.accounts for sensor_description in MONARCH_MONEY_SENSORS ) + + async_add_entities( + MonarchMoneyCashFlowSensor( + mm_coordinator, + sensor_description, + mm_coordinator.cashflow_summary, + ) + for sensor_description in MONARCH_CASHFLOW_SENSORS + ) diff --git a/homeassistant/components/monarchmoney/strings.json b/homeassistant/components/monarchmoney/strings.json index b702156b186cc6..ac2ee0135821ea 100644 --- a/homeassistant/components/monarchmoney/strings.json +++ b/homeassistant/components/monarchmoney/strings.json @@ -26,6 +26,19 @@ "age": { "name": "Data age" }, + + "sum_income": { + "name": "Income YTD" + }, + "sum_expense": { + "name": "Expense YTD" + }, + "savings": { + "name": "Savings YTD" + }, + "savings_rate": { + "name": "Savings rate" + }, "created": { "name": "First sync" } diff --git a/tests/components/monarchmoney/conftest.py b/tests/components/monarchmoney/conftest.py index 47087e616ffcbc..a2715131092bf5 100644 --- a/tests/components/monarchmoney/conftest.py +++ b/tests/components/monarchmoney/conftest.py @@ -37,6 +37,9 @@ def mock_config_api() -> Generator[AsyncMock]: """Mock the MonarchMoney class.""" account_data: dict[str, Any] = json.loads(load_fixture("get_accounts.json", DOMAIN)) + cashflow_summary: dict[str, Any] = json.loads( + load_fixture("get_cashflow_summary.json", DOMAIN) + ) with ( patch( @@ -62,4 +65,5 @@ def mock_config_api() -> Generator[AsyncMock]: } ) instance.get_accounts = AsyncMock(return_value=account_data) + instance.get_cashflow_summary = AsyncMock(return_value=cashflow_summary) yield mock_class diff --git a/tests/components/monarchmoney/fixtures/get_cashflow_summary.json b/tests/components/monarchmoney/fixtures/get_cashflow_summary.json new file mode 100644 index 00000000000000..a223782469affe --- /dev/null +++ b/tests/components/monarchmoney/fixtures/get_cashflow_summary.json @@ -0,0 +1,14 @@ +{ + "summary": [ + { + "summary": { + "sumIncome": 15000.0, + "sumExpense": -9000.0, + "savings": 6000.0, + "savingsRate": 0.4, + "__typename": "TransactionsSummary" + }, + "__typename": "AggregateData" + } + ] +} diff --git a/tests/components/monarchmoney/snapshots/test_sensor.ambr b/tests/components/monarchmoney/snapshots/test_sensor.ambr index 8435b09374bbb6..8cad49fed6750f 100644 --- a/tests/components/monarchmoney/snapshots/test_sensor.ambr +++ b/tests/components/monarchmoney/snapshots/test_sensor.ambr @@ -1,4 +1,207 @@ # serializer version: 1 +# name: test_all_entities[sensor.cashflow_expense_ytd-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.cashflow_expense_ytd', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Expense YTD', + 'platform': 'monarchmoney', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'sum_expense', + 'unique_id': 'monarch_money_cashflow_sum_expense', + 'unit_of_measurement': '$', + }) +# --- +# name: test_all_entities[sensor.cashflow_expense_ytd-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'monetary', + 'friendly_name': 'Cashflow Expense YTD', + 'state_class': , + 'unit_of_measurement': '$', + }), + 'context': , + 'entity_id': 'sensor.cashflow_expense_ytd', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '-9000.0', + }) +# --- +# name: test_all_entities[sensor.cashflow_income_ytd-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.cashflow_income_ytd', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Income YTD', + 'platform': 'monarchmoney', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'sum_income', + 'unique_id': 'monarch_money_cashflow_sum_income', + 'unit_of_measurement': '$', + }) +# --- +# name: test_all_entities[sensor.cashflow_income_ytd-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'monetary', + 'friendly_name': 'Cashflow Income YTD', + 'state_class': , + 'unit_of_measurement': '$', + }), + 'context': , + 'entity_id': 'sensor.cashflow_income_ytd', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '15000.0', + }) +# --- +# name: test_all_entities[sensor.cashflow_savings_rate-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.cashflow_savings_rate', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 1, + }), + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'Savings rate', + 'platform': 'monarchmoney', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'savings_rate', + 'unique_id': 'monarch_money_cashflow_savings_rate', + 'unit_of_measurement': '%', + }) +# --- +# name: test_all_entities[sensor.cashflow_savings_rate-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Cashflow Savings rate', + 'unit_of_measurement': '%', + }), + 'context': , + 'entity_id': 'sensor.cashflow_savings_rate', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '0.4', + }) +# --- +# name: test_all_entities[sensor.cashflow_savings_ytd-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.cashflow_savings_ytd', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Savings YTD', + 'platform': 'monarchmoney', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'savings', + 'unique_id': 'monarch_money_cashflow_savings', + 'unit_of_measurement': '$', + }) +# --- +# name: test_all_entities[sensor.cashflow_savings_ytd-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'monetary', + 'friendly_name': 'Cashflow Savings YTD', + 'state_class': , + 'unit_of_measurement': '$', + }), + 'context': , + 'entity_id': 'sensor.cashflow_savings_ytd', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '6000.0', + }) +# --- # name: test_all_entities[sensor.manual_entry_wallet_balance-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ From 0e7a80c9b920b1f0c9f9b376737e6b77bf5f9d5b Mon Sep 17 00:00:00 2001 From: Jeff Stein Date: Fri, 16 Aug 2024 15:23:26 -0600 Subject: [PATCH 11/67] tweak icons --- homeassistant/components/monarchmoney/sensor.py | 6 +++++- .../monarchmoney/snapshots/test_sensor.ambr | 14 +++++++++----- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/monarchmoney/sensor.py b/homeassistant/components/monarchmoney/sensor.py index aebc9e999c5db5..d422d323f49179 100644 --- a/homeassistant/components/monarchmoney/sensor.py +++ b/homeassistant/components/monarchmoney/sensor.py @@ -120,6 +120,7 @@ class MonarchMoneySensorEntityDescription(SensorEntityDescription): state_class=SensorStateClass.TOTAL, device_class=SensorDeviceClass.MONETARY, native_unit_of_measurement=CURRENCY_DOLLAR, + icon="mdi:cash-plus", ), MonarchMoneySensorEntityDescription( key="sum_expense", @@ -128,6 +129,7 @@ class MonarchMoneySensorEntityDescription(SensorEntityDescription): state_class=SensorStateClass.TOTAL, device_class=SensorDeviceClass.MONETARY, native_unit_of_measurement=CURRENCY_DOLLAR, + icon="mdi:cash-minus", ), MonarchMoneySensorEntityDescription( key="savings", @@ -136,13 +138,15 @@ class MonarchMoneySensorEntityDescription(SensorEntityDescription): state_class=SensorStateClass.TOTAL, device_class=SensorDeviceClass.MONETARY, native_unit_of_measurement=CURRENCY_DOLLAR, + icon="mdi:piggy-bank-outline", ), MonarchMoneySensorEntityDescription( key="savings_rate", translation_key="savings_rate", - value_fn=lambda summary: summary["savingsRate"], + value_fn=lambda summary: summary["savingsRate"] * 100, suggested_display_precision=1, native_unit_of_measurement=PERCENTAGE, + icon="mdi:cash-sync", ), ) diff --git a/tests/components/monarchmoney/snapshots/test_sensor.ambr b/tests/components/monarchmoney/snapshots/test_sensor.ambr index 8cad49fed6750f..3f685c93df0a2d 100644 --- a/tests/components/monarchmoney/snapshots/test_sensor.ambr +++ b/tests/components/monarchmoney/snapshots/test_sensor.ambr @@ -24,7 +24,7 @@ 'options': dict({ }), 'original_device_class': , - 'original_icon': None, + 'original_icon': 'mdi:cash-minus', 'original_name': 'Expense YTD', 'platform': 'monarchmoney', 'previous_unique_id': None, @@ -39,6 +39,7 @@ 'attributes': ReadOnlyDict({ 'device_class': 'monetary', 'friendly_name': 'Cashflow Expense YTD', + 'icon': 'mdi:cash-minus', 'state_class': , 'unit_of_measurement': '$', }), @@ -75,7 +76,7 @@ 'options': dict({ }), 'original_device_class': , - 'original_icon': None, + 'original_icon': 'mdi:cash-plus', 'original_name': 'Income YTD', 'platform': 'monarchmoney', 'previous_unique_id': None, @@ -90,6 +91,7 @@ 'attributes': ReadOnlyDict({ 'device_class': 'monetary', 'friendly_name': 'Cashflow Income YTD', + 'icon': 'mdi:cash-plus', 'state_class': , 'unit_of_measurement': '$', }), @@ -127,7 +129,7 @@ }), }), 'original_device_class': None, - 'original_icon': None, + 'original_icon': 'mdi:cash-sync', 'original_name': 'Savings rate', 'platform': 'monarchmoney', 'previous_unique_id': None, @@ -141,6 +143,7 @@ StateSnapshot({ 'attributes': ReadOnlyDict({ 'friendly_name': 'Cashflow Savings rate', + 'icon': 'mdi:cash-sync', 'unit_of_measurement': '%', }), 'context': , @@ -148,7 +151,7 @@ 'last_changed': , 'last_reported': , 'last_updated': , - 'state': '0.4', + 'state': '40.0', }) # --- # name: test_all_entities[sensor.cashflow_savings_ytd-entry] @@ -176,7 +179,7 @@ 'options': dict({ }), 'original_device_class': , - 'original_icon': None, + 'original_icon': 'mdi:piggy-bank-outline', 'original_name': 'Savings YTD', 'platform': 'monarchmoney', 'previous_unique_id': None, @@ -191,6 +194,7 @@ 'attributes': ReadOnlyDict({ 'device_class': 'monetary', 'friendly_name': 'Cashflow Savings YTD', + 'icon': 'mdi:piggy-bank-outline', 'state_class': , 'unit_of_measurement': '$', }), From d0bb94acd9d73a6e7eb5c4e298bf6d00b43d6240 Mon Sep 17 00:00:00 2001 From: Jeff Stein Date: Mon, 19 Aug 2024 21:37:43 -0600 Subject: [PATCH 12/67] refactor some type stuff as well as initialize the pr comment addressing process --- .../components/monarchmoney/__init__.py | 2 +- .../components/monarchmoney/coordinator.py | 38 ++++++++++++------- .../components/monarchmoney/entity.py | 1 - .../components/monarchmoney/sensor.py | 2 +- tests/components/monarchmoney/conftest.py | 5 +++ tests/components/wemo/entity_test_helpers.py | 2 +- 6 files changed, 32 insertions(+), 18 deletions(-) diff --git a/homeassistant/components/monarchmoney/__init__.py b/homeassistant/components/monarchmoney/__init__.py index 372e0316d9b863..54ae0f6443f63b 100644 --- a/homeassistant/components/monarchmoney/__init__.py +++ b/homeassistant/components/monarchmoney/__init__.py @@ -14,7 +14,7 @@ from .coordinator import MonarchMoneyDataUpdateCoordinator -MonarchMoneyConfigEntry = ConfigEntry[MonarchMoneyDataUpdateCoordinator] # noqa: F821 +type MonarchMoneyConfigEntry = ConfigEntry[MonarchMoneyDataUpdateCoordinator] PLATFORMS: list[Platform] = [Platform.SENSOR] diff --git a/homeassistant/components/monarchmoney/coordinator.py b/homeassistant/components/monarchmoney/coordinator.py index 9f7f22a6a1d032..057fb17a838db2 100644 --- a/homeassistant/components/monarchmoney/coordinator.py +++ b/homeassistant/components/monarchmoney/coordinator.py @@ -1,24 +1,32 @@ """Data coordinator for monarch money.""" +from dataclasses import dataclass from datetime import timedelta from typing import Any from monarchmoney import MonarchMoney from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant from homeassistant.helpers.update_coordinator import DataUpdateCoordinator from .const import LOGGER -AccountData = dict[str, Any] +@dataclass +class MonarchData: + """Data class to hold monarch data.""" -class MonarchMoneyDataUpdateCoordinator(DataUpdateCoordinator[AccountData]): + account_data: list[dict[str, Any]] + cashflow_summary: dict[str, Any] + + +class MonarchMoneyDataUpdateCoordinator(DataUpdateCoordinator[MonarchData]): """Data update coordinator for Monarch Money.""" config_entry: ConfigEntry - def __init__(self, hass, client): + def __init__(self, hass: HomeAssistant, client: MonarchMoney) -> None: """Initialize the coordinator.""" super().__init__( hass=hass, @@ -28,29 +36,31 @@ def __init__(self, hass, client): ) self.client: MonarchMoney = client - async def _async_update_data(self) -> Any: + async def _async_update_data(self) -> MonarchData: """Fetch data for all accounts.""" account_data = await self.client.get_accounts() cashflow_summary = await self.client.get_cashflow_summary() - return { - "account_data": account_data, - "cashflow_summary": cashflow_summary, - } + + return MonarchData( + account_data=account_data["accounts"], + cashflow_summary=cashflow_summary["summary"][0]["summary"], + ) @property - def cashflow_summary(self) -> Any: + def cashflow_summary(self) -> dict[str, Any]: """Return cashflow summary.""" - return self.data["cashflow_summary"]["summary"][0]["summary"] + return self.data.cashflow_summary @property - def accounts(self) -> Any: + def accounts(self) -> list[dict[str, Any]]: """Return accounts.""" - return self.data["account_data"]["accounts"] - def get_account_for_id(self, account_id: str) -> Any | None: + return self.data.account_data + + def get_account_for_id(self, account_id: str) -> dict[str, Any] | None: """Get account for id.""" - for account in self.data["account_data"]["accounts"]: + for account in self.data.account_data: if account["id"] == account_id: return account return None diff --git a/homeassistant/components/monarchmoney/entity.py b/homeassistant/components/monarchmoney/entity.py index 40f4a1a45962dd..75d294143df60e 100644 --- a/homeassistant/components/monarchmoney/entity.py +++ b/homeassistant/components/monarchmoney/entity.py @@ -54,7 +54,6 @@ def __init__( super().__init__(coordinator) self.entity_description = description - self._account_id = account["id"] # Parse out some fields diff --git a/homeassistant/components/monarchmoney/sensor.py b/homeassistant/components/monarchmoney/sensor.py index d422d323f49179..ef009da117ab68 100644 --- a/homeassistant/components/monarchmoney/sensor.py +++ b/homeassistant/components/monarchmoney/sensor.py @@ -65,7 +65,7 @@ def _type_to_icon(account: Any) -> str: "vehicle": "mdi:car", } if account_subtype not in icon_mapping.get(account_type, {}): - LOGGER.info( + LOGGER.debug( f"Unknown subtype '{account_subtype}' for account type '{account_type}'" ) return default_icons.get(account_type, "mdi:cash") diff --git a/tests/components/monarchmoney/conftest.py b/tests/components/monarchmoney/conftest.py index a2715131092bf5..247fb8cc2ecce3 100644 --- a/tests/components/monarchmoney/conftest.py +++ b/tests/components/monarchmoney/conftest.py @@ -41,6 +41,11 @@ def mock_config_api() -> Generator[AsyncMock]: load_fixture("get_cashflow_summary.json", DOMAIN) ) + # monarch_data = MonarchData( + # account_data=account_data["accounts"], + # cashflow_summary=cashflow_summary["summary"][0]["summary"], + # ) + with ( patch( "homeassistant.components.monarchmoney.config_flow.MonarchMoney", diff --git a/tests/components/wemo/entity_test_helpers.py b/tests/components/wemo/entity_test_helpers.py index f57dffad6f9cf3..ff223f8e026ab8 100644 --- a/tests/components/wemo/entity_test_helpers.py +++ b/tests/components/wemo/entity_test_helpers.py @@ -40,7 +40,7 @@ def _perform_async_update(coordinator): """Return a callable method to cause hass to update the state of the entity.""" async def async_callback(): - await coordinator._async_update_data() + await coordinator._async_update_data return async_callback From 972fa94d3730490ddbfadca1b5efb65687253b06 Mon Sep 17 00:00:00 2001 From: Jeff Stein Date: Mon, 19 Aug 2024 21:38:55 -0600 Subject: [PATCH 13/67] remove empty fields from manifest --- homeassistant/components/monarchmoney/manifest.json | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/homeassistant/components/monarchmoney/manifest.json b/homeassistant/components/monarchmoney/manifest.json index a28ff06c02b02b..814e0ce5eee427 100644 --- a/homeassistant/components/monarchmoney/manifest.json +++ b/homeassistant/components/monarchmoney/manifest.json @@ -3,11 +3,7 @@ "name": "Monarch Money", "codeowners": ["@jeeftor"], "config_flow": true, - "dependencies": [], "documentation": "https://www.home-assistant.io/integrations/monarchmoney", - "homekit": {}, "iot_class": "cloud_polling", - "requirements": ["monarchmoney==0.1.13"], - "ssdp": [], - "zeroconf": [] + "requirements": ["monarchmoney==0.1.13"] } From 2cc69b629bab4e45a740db66f4585bf520dd0120 Mon Sep 17 00:00:00 2001 From: Jeef Date: Mon, 19 Aug 2024 21:39:59 -0600 Subject: [PATCH 14/67] Update homeassistant/components/monarchmoney/sensor.py Co-authored-by: Joost Lekkerkerker --- homeassistant/components/monarchmoney/sensor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/monarchmoney/sensor.py b/homeassistant/components/monarchmoney/sensor.py index ef009da117ab68..0c383bf9d75123 100644 --- a/homeassistant/components/monarchmoney/sensor.py +++ b/homeassistant/components/monarchmoney/sensor.py @@ -157,7 +157,7 @@ class MonarchMoneyCashFlowSensor(MonarchMoneyBaseEntity, SensorEntity): entity_description: MonarchMoneySensorEntityDescription @property - def native_value(self) -> StateType | datetime | None: + def native_value(self) -> StateType | datetime: """Return the state.""" return self.entity_description.value_fn(self.summary_data) From 296fd4f9a126594c39cc52f7b0117d3ba95956a8 Mon Sep 17 00:00:00 2001 From: Jeff Stein Date: Mon, 19 Aug 2024 21:42:38 -0600 Subject: [PATCH 15/67] move stuff --- .../components/monarchmoney/sensor.py | 56 +++++++++---------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/homeassistant/components/monarchmoney/sensor.py b/homeassistant/components/monarchmoney/sensor.py index 0c383bf9d75123..d48d6263662d7a 100644 --- a/homeassistant/components/monarchmoney/sensor.py +++ b/homeassistant/components/monarchmoney/sensor.py @@ -151,6 +151,34 @@ class MonarchMoneySensorEntityDescription(SensorEntityDescription): ) +async def async_setup_entry( + hass: HomeAssistant, + config_entry: MonarchMoneyConfigEntry, + async_add_entities: AddEntitiesCallback, +) -> None: + """Set up Monarch Money sensors for config entries.""" + mm_coordinator = config_entry.runtime_data + + async_add_entities( + MonarchMoneySensor( + mm_coordinator, + sensor_description, + account, + ) + for account in mm_coordinator.accounts + for sensor_description in MONARCH_MONEY_SENSORS + ) + + async_add_entities( + MonarchMoneyCashFlowSensor( + mm_coordinator, + sensor_description, + mm_coordinator.cashflow_summary, + ) + for sensor_description in MONARCH_CASHFLOW_SENSORS + ) + + class MonarchMoneyCashFlowSensor(MonarchMoneyBaseEntity, SensorEntity): """Cashflow summary sensor.""" @@ -185,31 +213,3 @@ def icon(self) -> str | None: if self.entity_description.icon_fn is not None: return self.entity_description.icon_fn(self.account_data) return None - - -async def async_setup_entry( - hass: HomeAssistant, - config_entry: MonarchMoneyConfigEntry, - async_add_entities: AddEntitiesCallback, -) -> None: - """Set up Monarch Money sensors for config entries.""" - mm_coordinator = config_entry.runtime_data - - async_add_entities( - MonarchMoneySensor( - mm_coordinator, - sensor_description, - account, - ) - for account in mm_coordinator.accounts - for sensor_description in MONARCH_MONEY_SENSORS - ) - - async_add_entities( - MonarchMoneyCashFlowSensor( - mm_coordinator, - sensor_description, - mm_coordinator.cashflow_summary, - ) - for sensor_description in MONARCH_CASHFLOW_SENSORS - ) From 817f40a082d96c56fc07207af3bab011e2b4b193 Mon Sep 17 00:00:00 2001 From: Jeff Stein Date: Mon, 19 Aug 2024 21:43:51 -0600 Subject: [PATCH 16/67] get logging out of try block --- homeassistant/components/monarchmoney/config_flow.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/monarchmoney/config_flow.py b/homeassistant/components/monarchmoney/config_flow.py index 7a05eba0cd993b..ec4b2ba2646195 100644 --- a/homeassistant/components/monarchmoney/config_flow.py +++ b/homeassistant/components/monarchmoney/config_flow.py @@ -64,16 +64,16 @@ async def validate_login( monarch_client = MonarchMoney() if CONF_MFA_CODE in data: mfa_code = data[CONF_MFA_CODE] + LOGGER.debug("Attempting to authenticate with MFA code") try: - LOGGER.debug("Attempting to authenticate with MFA code") await monarch_client.multi_factor_authenticate(email, password, mfa_code) except KeyError: # A bug in the backing lib that I don't control throws a KeyError if the MFA code is wrong LOGGER.debug("Bad MFA Code") raise BadMFA from None else: + LOGGER.debug("Attempting to authenticate") try: - LOGGER.debug("Attempting to authenticate") await monarch_client.login( email=email, password=password, From 1acadabe27aea4de8983cbbfabd7fce9bb722397 Mon Sep 17 00:00:00 2001 From: Jeff Stein Date: Mon, 19 Aug 2024 21:45:55 -0600 Subject: [PATCH 17/67] get logging out of try block --- homeassistant/components/monarchmoney/config_flow.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/monarchmoney/config_flow.py b/homeassistant/components/monarchmoney/config_flow.py index ec4b2ba2646195..d258a601df5cbc 100644 --- a/homeassistant/components/monarchmoney/config_flow.py +++ b/homeassistant/components/monarchmoney/config_flow.py @@ -67,10 +67,10 @@ async def validate_login( LOGGER.debug("Attempting to authenticate with MFA code") try: await monarch_client.multi_factor_authenticate(email, password, mfa_code) - except KeyError: + except KeyError as err: # A bug in the backing lib that I don't control throws a KeyError if the MFA code is wrong LOGGER.debug("Bad MFA Code") - raise BadMFA from None + raise BadMFA from err else: LOGGER.debug("Attempting to authenticate") try: From dc6d69ea3e7cf3798c779c398d6185a0db504f47 Mon Sep 17 00:00:00 2001 From: Jeff Stein Date: Mon, 19 Aug 2024 22:03:24 -0600 Subject: [PATCH 18/67] using Subscription ID as stored in config entry for unique id soon --- .../components/monarchmoney/config_flow.py | 14 ++++++++++--- .../components/monarchmoney/sensor.py | 20 +++++++++---------- tests/components/monarchmoney/conftest.py | 7 ++----- .../fixtures/get_subscription_details.json | 10 ++++++++++ .../monarchmoney/test_config_flow.py | 5 ++++- 5 files changed, 37 insertions(+), 19 deletions(-) create mode 100644 tests/components/monarchmoney/fixtures/get_subscription_details.json diff --git a/homeassistant/components/monarchmoney/config_flow.py b/homeassistant/components/monarchmoney/config_flow.py index d258a601df5cbc..79e24d25347811 100644 --- a/homeassistant/components/monarchmoney/config_flow.py +++ b/homeassistant/components/monarchmoney/config_flow.py @@ -10,7 +10,7 @@ import voluptuous as vol from homeassistant.config_entries import ConfigFlow, ConfigFlowResult -from homeassistant.const import CONF_EMAIL, CONF_PASSWORD, CONF_TOKEN +from homeassistant.const import CONF_EMAIL, CONF_ID, CONF_PASSWORD, CONF_TOKEN from homeassistant.core import HomeAssistant from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers.selector import ( @@ -86,7 +86,14 @@ async def validate_login( raise InvalidAuth from err LOGGER.debug(f"Connection successful - saving session to file {SESSION_FILE}") - return {"title": "Monarch Money", CONF_TOKEN: monarch_client.token} + LOGGER.debug("Obtaining subscription id") + subs = await monarch_client.get_subscription_details() + subscription_id = subs["subscription"]["id"] + return { + "title": "Monarch Money", + CONF_TOKEN: monarch_client.token, + CONF_ID: subscription_id, + } class MonarchMoneyConfigFlow(ConfigFlow, domain=DOMAIN): @@ -129,7 +136,8 @@ async def async_step_user( errors["base"] = "invalid_auth" else: return self.async_create_entry( - title=info["title"], data={CONF_TOKEN: info[CONF_TOKEN]} + title=info["title"], + data={CONF_TOKEN: info[CONF_TOKEN], CONF_ID: info[CONF_ID]}, ) return self.async_show_form( diff --git a/homeassistant/components/monarchmoney/sensor.py b/homeassistant/components/monarchmoney/sensor.py index d48d6263662d7a..f98858200dceec 100644 --- a/homeassistant/components/monarchmoney/sensor.py +++ b/homeassistant/components/monarchmoney/sensor.py @@ -159,7 +159,14 @@ async def async_setup_entry( """Set up Monarch Money sensors for config entries.""" mm_coordinator = config_entry.runtime_data - async_add_entities( + entity_list = [ + MonarchMoneyCashFlowSensor( + mm_coordinator, + sensor_description, + mm_coordinator.cashflow_summary, + ) + for sensor_description in MONARCH_CASHFLOW_SENSORS + ] + [ MonarchMoneySensor( mm_coordinator, sensor_description, @@ -167,16 +174,9 @@ async def async_setup_entry( ) for account in mm_coordinator.accounts for sensor_description in MONARCH_MONEY_SENSORS - ) + ] - async_add_entities( - MonarchMoneyCashFlowSensor( - mm_coordinator, - sensor_description, - mm_coordinator.cashflow_summary, - ) - for sensor_description in MONARCH_CASHFLOW_SENSORS - ) + async_add_entities(entity_list) class MonarchMoneyCashFlowSensor(MonarchMoneyBaseEntity, SensorEntity): diff --git a/tests/components/monarchmoney/conftest.py b/tests/components/monarchmoney/conftest.py index 247fb8cc2ecce3..bcccb71805b23a 100644 --- a/tests/components/monarchmoney/conftest.py +++ b/tests/components/monarchmoney/conftest.py @@ -40,11 +40,7 @@ def mock_config_api() -> Generator[AsyncMock]: cashflow_summary: dict[str, Any] = json.loads( load_fixture("get_cashflow_summary.json", DOMAIN) ) - - # monarch_data = MonarchData( - # account_data=account_data["accounts"], - # cashflow_summary=cashflow_summary["summary"][0]["summary"], - # ) + subs = json.loads(load_fixture("get_subscription_details.json", DOMAIN)) with ( patch( @@ -71,4 +67,5 @@ def mock_config_api() -> Generator[AsyncMock]: ) instance.get_accounts = AsyncMock(return_value=account_data) instance.get_cashflow_summary = AsyncMock(return_value=cashflow_summary) + instance.get_subscription_details = AsyncMock(return_value=subs) yield mock_class diff --git a/tests/components/monarchmoney/fixtures/get_subscription_details.json b/tests/components/monarchmoney/fixtures/get_subscription_details.json new file mode 100644 index 00000000000000..16f90a2ca38b57 --- /dev/null +++ b/tests/components/monarchmoney/fixtures/get_subscription_details.json @@ -0,0 +1,10 @@ +{ + "subscription": { + "id": "222260252323873333", + "paymentSource": "STRIPE", + "referralCode": "go3dpvrdmw", + "isOnFreeTrial": true, + "hasPremiumEntitlement": true, + "__typename": "HouseholdSubscription" + } +} diff --git a/tests/components/monarchmoney/test_config_flow.py b/tests/components/monarchmoney/test_config_flow.py index 01166a2293b04a..fe790f9b3ecca2 100644 --- a/tests/components/monarchmoney/test_config_flow.py +++ b/tests/components/monarchmoney/test_config_flow.py @@ -6,7 +6,7 @@ from homeassistant import config_entries from homeassistant.components.monarchmoney.const import CONF_MFA_CODE, DOMAIN -from homeassistant.const import CONF_EMAIL, CONF_PASSWORD, CONF_TOKEN +from homeassistant.const import CONF_EMAIL, CONF_ID, CONF_PASSWORD, CONF_TOKEN from homeassistant.core import HomeAssistant from homeassistant.data_entry_flow import FlowResultType @@ -34,6 +34,7 @@ async def test_form_simple( assert result["title"] == "Monarch Money" assert result["data"] == { CONF_TOKEN: "mocked_token", + CONF_ID: "222260252323873333", } assert len(mock_setup_entry.mock_calls) == 1 @@ -79,6 +80,7 @@ async def test_form_invalid_auth( assert result["title"] == "Monarch Money" assert result["data"] == { CONF_TOKEN: "mocked_token", + CONF_ID: "222260252323873333", } assert len(mock_setup_entry.mock_calls) == 1 @@ -137,5 +139,6 @@ async def test_form_mfa( assert result["title"] == "Monarch Money" assert result["data"] == { CONF_TOKEN: "mocked_token", + CONF_ID: "222260252323873333", } assert len(mock_setup_entry.mock_calls) == 1 From bd7fb3aac87f84295c3c1dead6087dc048207d76 Mon Sep 17 00:00:00 2001 From: Jeff Stein Date: Mon, 19 Aug 2024 22:26:58 -0600 Subject: [PATCH 19/67] new unique id --- .../components/monarchmoney/__init__.py | 8 ++- .../components/monarchmoney/config_flow.py | 6 ++- .../components/monarchmoney/coordinator.py | 5 +- .../components/monarchmoney/entity.py | 4 +- tests/components/monarchmoney/conftest.py | 1 + .../monarchmoney/snapshots/test_sensor.ambr | 54 +++++++++---------- .../monarchmoney/test_config_flow.py | 9 ++-- 7 files changed, 48 insertions(+), 39 deletions(-) diff --git a/homeassistant/components/monarchmoney/__init__.py b/homeassistant/components/monarchmoney/__init__.py index 54ae0f6443f63b..fb210c44d84195 100644 --- a/homeassistant/components/monarchmoney/__init__.py +++ b/homeassistant/components/monarchmoney/__init__.py @@ -26,11 +26,15 @@ async def async_setup_entry( monarch_client = MonarchMoney(token=entry.data.get(CONF_TOKEN)) try: - await monarch_client.get_subscription_details() + sub_details = await monarch_client.get_subscription_details() except (TransportServerError, LoginFailedException, ClientResponseError) as err: raise ConfigEntryError("Authentication failed") from err - mm_coordinator = MonarchMoneyDataUpdateCoordinator(hass, monarch_client) + subscription_id = sub_details["subscription"]["id"] + + mm_coordinator = MonarchMoneyDataUpdateCoordinator( + hass, monarch_client, subscription_id + ) await mm_coordinator.async_config_entry_first_refresh() entry.runtime_data = mm_coordinator await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) diff --git a/homeassistant/components/monarchmoney/config_flow.py b/homeassistant/components/monarchmoney/config_flow.py index 79e24d25347811..7d894f32fa5070 100644 --- a/homeassistant/components/monarchmoney/config_flow.py +++ b/homeassistant/components/monarchmoney/config_flow.py @@ -135,11 +135,13 @@ async def async_step_user( except InvalidAuth: errors["base"] = "invalid_auth" else: + await self.async_set_unique_id(info[CONF_ID]) + self._abort_if_unique_id_configured() + return self.async_create_entry( title=info["title"], - data={CONF_TOKEN: info[CONF_TOKEN], CONF_ID: info[CONF_ID]}, + data={CONF_TOKEN: info[CONF_TOKEN]}, ) - return self.async_show_form( step_id="user", data_schema=STEP_USER_DATA_SCHEMA, errors=errors ) diff --git a/homeassistant/components/monarchmoney/coordinator.py b/homeassistant/components/monarchmoney/coordinator.py index 057fb17a838db2..0d40ab20e426a0 100644 --- a/homeassistant/components/monarchmoney/coordinator.py +++ b/homeassistant/components/monarchmoney/coordinator.py @@ -26,7 +26,9 @@ class MonarchMoneyDataUpdateCoordinator(DataUpdateCoordinator[MonarchData]): config_entry: ConfigEntry - def __init__(self, hass: HomeAssistant, client: MonarchMoney) -> None: + def __init__( + self, hass: HomeAssistant, client: MonarchMoney, subscription_id: str + ) -> None: """Initialize the coordinator.""" super().__init__( hass=hass, @@ -35,6 +37,7 @@ def __init__(self, hass: HomeAssistant, client: MonarchMoney) -> None: update_interval=timedelta(hours=4), ) self.client: MonarchMoney = client + self.subscription_id = subscription_id async def _async_update_data(self) -> MonarchData: """Fetch data for all accounts.""" diff --git a/homeassistant/components/monarchmoney/entity.py b/homeassistant/components/monarchmoney/entity.py index 75d294143df60e..3cf0ee6dee0597 100644 --- a/homeassistant/components/monarchmoney/entity.py +++ b/homeassistant/components/monarchmoney/entity.py @@ -72,9 +72,7 @@ def __init__( if not configuration_url.startswith(("http://", "https://")): configuration_url = f"http://{configuration_url}" - self._attr_unique_id = ( - f"{institution}_{account["displayName"]}_{description.translation_key}" - ) + self._attr_unique_id = f"{coordinator.subscription_id}_{account["displayName"]}_{description.translation_key}" atype = account["type"]["display"] asubtype = account["subtype"]["display"] diff --git a/tests/components/monarchmoney/conftest.py b/tests/components/monarchmoney/conftest.py index bcccb71805b23a..5b41367def94ed 100644 --- a/tests/components/monarchmoney/conftest.py +++ b/tests/components/monarchmoney/conftest.py @@ -28,6 +28,7 @@ async def mock_config_entry() -> MockConfigEntry: return MockConfigEntry( domain=DOMAIN, data={CONF_TOKEN: "fake_token_of_doom"}, + unique_id="222260252323873333", version=1, ) diff --git a/tests/components/monarchmoney/snapshots/test_sensor.ambr b/tests/components/monarchmoney/snapshots/test_sensor.ambr index 3f685c93df0a2d..092bcece3961b5 100644 --- a/tests/components/monarchmoney/snapshots/test_sensor.ambr +++ b/tests/components/monarchmoney/snapshots/test_sensor.ambr @@ -237,7 +237,7 @@ 'previous_unique_id': None, 'supported_features': 0, 'translation_key': 'balance', - 'unique_id': 'Manual entry_Wallet_balance', + 'unique_id': '222260252323873333_Wallet_balance', 'unit_of_measurement': '$', }) # --- @@ -288,7 +288,7 @@ 'previous_unique_id': None, 'supported_features': 0, 'translation_key': 'age', - 'unique_id': 'Manual entry_Wallet_age', + 'unique_id': '222260252323873333_Wallet_age', 'unit_of_measurement': None, }) # --- @@ -336,7 +336,7 @@ 'previous_unique_id': None, 'supported_features': 0, 'translation_key': 'created', - 'unique_id': 'Manual entry_Wallet_created', + 'unique_id': '222260252323873333_Wallet_created', 'unit_of_measurement': None, }) # --- @@ -386,7 +386,7 @@ 'previous_unique_id': None, 'supported_features': 0, 'translation_key': 'balance', - 'unique_id': 'Rando Bank_Checking_balance', + 'unique_id': '222260252323873333_Checking_balance', 'unit_of_measurement': '$', }) # --- @@ -438,7 +438,7 @@ 'previous_unique_id': None, 'supported_features': 0, 'translation_key': 'age', - 'unique_id': 'Rando Bank_Checking_age', + 'unique_id': '222260252323873333_Checking_age', 'unit_of_measurement': None, }) # --- @@ -486,7 +486,7 @@ 'previous_unique_id': None, 'supported_features': 0, 'translation_key': 'created', - 'unique_id': 'Rando Bank_Checking_created', + 'unique_id': '222260252323873333_Checking_created', 'unit_of_measurement': None, }) # --- @@ -536,7 +536,7 @@ 'previous_unique_id': None, 'supported_features': 0, 'translation_key': 'balance', - 'unique_id': 'Rando Brokerage_Brokerage_balance', + 'unique_id': '222260252323873333_Brokerage_balance', 'unit_of_measurement': '$', }) # --- @@ -588,7 +588,7 @@ 'previous_unique_id': None, 'supported_features': 0, 'translation_key': 'age', - 'unique_id': 'Rando Brokerage_Brokerage_age', + 'unique_id': '222260252323873333_Brokerage_age', 'unit_of_measurement': None, }) # --- @@ -636,7 +636,7 @@ 'previous_unique_id': None, 'supported_features': 0, 'translation_key': 'created', - 'unique_id': 'Rando Brokerage_Brokerage_created', + 'unique_id': '222260252323873333_Brokerage_created', 'unit_of_measurement': None, }) # --- @@ -686,7 +686,7 @@ 'previous_unique_id': None, 'supported_features': 0, 'translation_key': 'balance', - 'unique_id': 'Rando Credit_Credit Card_balance', + 'unique_id': '222260252323873333_Credit Card_balance', 'unit_of_measurement': '$', }) # --- @@ -738,7 +738,7 @@ 'previous_unique_id': None, 'supported_features': 0, 'translation_key': 'age', - 'unique_id': 'Rando Credit_Credit Card_age', + 'unique_id': '222260252323873333_Credit Card_age', 'unit_of_measurement': None, }) # --- @@ -786,7 +786,7 @@ 'previous_unique_id': None, 'supported_features': 0, 'translation_key': 'created', - 'unique_id': 'Rando Credit_Credit Card_created', + 'unique_id': '222260252323873333_Credit Card_created', 'unit_of_measurement': None, }) # --- @@ -836,7 +836,7 @@ 'previous_unique_id': None, 'supported_features': 0, 'translation_key': 'balance', - 'unique_id': 'Rando Employer Investments_401.k_balance', + 'unique_id': '222260252323873333_401.k_balance', 'unit_of_measurement': '$', }) # --- @@ -888,7 +888,7 @@ 'previous_unique_id': None, 'supported_features': 0, 'translation_key': 'age', - 'unique_id': 'Rando Employer Investments_401.k_age', + 'unique_id': '222260252323873333_401.k_age', 'unit_of_measurement': None, }) # --- @@ -936,7 +936,7 @@ 'previous_unique_id': None, 'supported_features': 0, 'translation_key': 'created', - 'unique_id': 'Rando Employer Investments_401.k_created', + 'unique_id': '222260252323873333_401.k_created', 'unit_of_measurement': None, }) # --- @@ -986,7 +986,7 @@ 'previous_unique_id': None, 'supported_features': 0, 'translation_key': 'balance', - 'unique_id': 'Rando Investments_Roth IRA_balance', + 'unique_id': '222260252323873333_Roth IRA_balance', 'unit_of_measurement': '$', }) # --- @@ -1038,7 +1038,7 @@ 'previous_unique_id': None, 'supported_features': 0, 'translation_key': 'age', - 'unique_id': 'Rando Investments_Roth IRA_age', + 'unique_id': '222260252323873333_Roth IRA_age', 'unit_of_measurement': None, }) # --- @@ -1086,7 +1086,7 @@ 'previous_unique_id': None, 'supported_features': 0, 'translation_key': 'created', - 'unique_id': 'Rando Investments_Roth IRA_created', + 'unique_id': '222260252323873333_Roth IRA_created', 'unit_of_measurement': None, }) # --- @@ -1136,7 +1136,7 @@ 'previous_unique_id': None, 'supported_features': 0, 'translation_key': 'balance', - 'unique_id': 'Rando Mortgage_Mortgage_balance', + 'unique_id': '222260252323873333_Mortgage_balance', 'unit_of_measurement': '$', }) # --- @@ -1188,7 +1188,7 @@ 'previous_unique_id': None, 'supported_features': 0, 'translation_key': 'age', - 'unique_id': 'Rando Mortgage_Mortgage_age', + 'unique_id': '222260252323873333_Mortgage_age', 'unit_of_measurement': None, }) # --- @@ -1236,7 +1236,7 @@ 'previous_unique_id': None, 'supported_features': 0, 'translation_key': 'created', - 'unique_id': 'Rando Mortgage_Mortgage_created', + 'unique_id': '222260252323873333_Mortgage_created', 'unit_of_measurement': None, }) # --- @@ -1286,7 +1286,7 @@ 'previous_unique_id': None, 'supported_features': 0, 'translation_key': 'balance', - 'unique_id': 'VinAudit_2050 Toyota RAV8_balance', + 'unique_id': '222260252323873333_2050 Toyota RAV8_balance', 'unit_of_measurement': '$', }) # --- @@ -1338,7 +1338,7 @@ 'previous_unique_id': None, 'supported_features': 0, 'translation_key': 'age', - 'unique_id': 'VinAudit_2050 Toyota RAV8_age', + 'unique_id': '222260252323873333_2050 Toyota RAV8_age', 'unit_of_measurement': None, }) # --- @@ -1386,7 +1386,7 @@ 'previous_unique_id': None, 'supported_features': 0, 'translation_key': 'created', - 'unique_id': 'VinAudit_2050 Toyota RAV8_created', + 'unique_id': '222260252323873333_2050 Toyota RAV8_created', 'unit_of_measurement': None, }) # --- @@ -1436,7 +1436,7 @@ 'previous_unique_id': None, 'supported_features': 0, 'translation_key': 'balance', - 'unique_id': 'Zillow_House_balance', + 'unique_id': '222260252323873333_House_balance', 'unit_of_measurement': '$', }) # --- @@ -1488,7 +1488,7 @@ 'previous_unique_id': None, 'supported_features': 0, 'translation_key': 'age', - 'unique_id': 'Zillow_House_age', + 'unique_id': '222260252323873333_House_age', 'unit_of_measurement': None, }) # --- @@ -1536,7 +1536,7 @@ 'previous_unique_id': None, 'supported_features': 0, 'translation_key': 'created', - 'unique_id': 'Zillow_House_created', + 'unique_id': '222260252323873333_House_created', 'unit_of_measurement': None, }) # --- diff --git a/tests/components/monarchmoney/test_config_flow.py b/tests/components/monarchmoney/test_config_flow.py index fe790f9b3ecca2..d3903a37073c5a 100644 --- a/tests/components/monarchmoney/test_config_flow.py +++ b/tests/components/monarchmoney/test_config_flow.py @@ -6,7 +6,7 @@ from homeassistant import config_entries from homeassistant.components.monarchmoney.const import CONF_MFA_CODE, DOMAIN -from homeassistant.const import CONF_EMAIL, CONF_ID, CONF_PASSWORD, CONF_TOKEN +from homeassistant.const import CONF_EMAIL, CONF_PASSWORD, CONF_TOKEN from homeassistant.core import HomeAssistant from homeassistant.data_entry_flow import FlowResultType @@ -34,8 +34,8 @@ async def test_form_simple( assert result["title"] == "Monarch Money" assert result["data"] == { CONF_TOKEN: "mocked_token", - CONF_ID: "222260252323873333", } + assert result["context"]["unique_id"] == "222260252323873333" assert len(mock_setup_entry.mock_calls) == 1 @@ -80,8 +80,8 @@ async def test_form_invalid_auth( assert result["title"] == "Monarch Money" assert result["data"] == { CONF_TOKEN: "mocked_token", - CONF_ID: "222260252323873333", } + assert result["context"]["unique_id"] == "222260252323873333" assert len(mock_setup_entry.mock_calls) == 1 @@ -139,6 +139,7 @@ async def test_form_mfa( assert result["title"] == "Monarch Money" assert result["data"] == { CONF_TOKEN: "mocked_token", - CONF_ID: "222260252323873333", } + assert result["context"]["unique_id"] == "222260252323873333" + assert len(mock_setup_entry.mock_calls) == 1 From 93320f8621dca4d457d0d1b7ae0e35236b74d4e0 Mon Sep 17 00:00:00 2001 From: Jeff Stein Date: Mon, 19 Aug 2024 22:31:00 -0600 Subject: [PATCH 20/67] giving cashflow a better unique id --- homeassistant/components/monarchmoney/entity.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/monarchmoney/entity.py b/homeassistant/components/monarchmoney/entity.py index 3cf0ee6dee0597..64f11b2add7d47 100644 --- a/homeassistant/components/monarchmoney/entity.py +++ b/homeassistant/components/monarchmoney/entity.py @@ -23,7 +23,9 @@ def __init__( ) -> None: """Initialize the Monarch Money Entity.""" super().__init__(coordinator) - self._attr_unique_id = f"monarch_money_cashflow_{description.key}" + self._attr_unique_id = ( + f"{coordinator.subscription_id}_cashflow_{description.key}" + ) self._data = data self.entity_description = description From 3c6f22808bb94738e8f1585260b16ae89ffade44 Mon Sep 17 00:00:00 2001 From: Jeff Stein Date: Mon, 19 Aug 2024 22:34:15 -0600 Subject: [PATCH 21/67] Moving subscription id stuff into setup of coordinator --- .../components/monarchmoney/__init__.py | 15 +------------- .../components/monarchmoney/coordinator.py | 20 ++++++++++++++++--- .../monarchmoney/snapshots/test_sensor.ambr | 8 ++++---- 3 files changed, 22 insertions(+), 21 deletions(-) diff --git a/homeassistant/components/monarchmoney/__init__.py b/homeassistant/components/monarchmoney/__init__.py index fb210c44d84195..a93722e7c2c700 100644 --- a/homeassistant/components/monarchmoney/__init__.py +++ b/homeassistant/components/monarchmoney/__init__.py @@ -2,15 +2,11 @@ from __future__ import annotations -from aiohttp import ClientResponseError -from gql.transport.exceptions import TransportServerError from monarchmoney import MonarchMoney -from monarchmoney.monarchmoney import LoginFailedException from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_TOKEN, Platform from homeassistant.core import HomeAssistant -from homeassistant.exceptions import ConfigEntryError from .coordinator import MonarchMoneyDataUpdateCoordinator @@ -25,16 +21,7 @@ async def async_setup_entry( """Set up Monarch Money from a config entry.""" monarch_client = MonarchMoney(token=entry.data.get(CONF_TOKEN)) - try: - sub_details = await monarch_client.get_subscription_details() - except (TransportServerError, LoginFailedException, ClientResponseError) as err: - raise ConfigEntryError("Authentication failed") from err - - subscription_id = sub_details["subscription"]["id"] - - mm_coordinator = MonarchMoneyDataUpdateCoordinator( - hass, monarch_client, subscription_id - ) + mm_coordinator = MonarchMoneyDataUpdateCoordinator(hass, monarch_client) await mm_coordinator.async_config_entry_first_refresh() entry.runtime_data = mm_coordinator await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) diff --git a/homeassistant/components/monarchmoney/coordinator.py b/homeassistant/components/monarchmoney/coordinator.py index 0d40ab20e426a0..d837973b2e4239 100644 --- a/homeassistant/components/monarchmoney/coordinator.py +++ b/homeassistant/components/monarchmoney/coordinator.py @@ -4,10 +4,13 @@ from datetime import timedelta from typing import Any -from monarchmoney import MonarchMoney +from aiohttp import ClientResponseError +from gql.transport.exceptions import TransportServerError +from monarchmoney import LoginFailedException, MonarchMoney from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant +from homeassistant.exceptions import ConfigEntryError from homeassistant.helpers.update_coordinator import DataUpdateCoordinator from .const import LOGGER @@ -27,7 +30,9 @@ class MonarchMoneyDataUpdateCoordinator(DataUpdateCoordinator[MonarchData]): config_entry: ConfigEntry def __init__( - self, hass: HomeAssistant, client: MonarchMoney, subscription_id: str + self, + hass: HomeAssistant, + client: MonarchMoney, ) -> None: """Initialize the coordinator.""" super().__init__( @@ -37,7 +42,16 @@ def __init__( update_interval=timedelta(hours=4), ) self.client: MonarchMoney = client - self.subscription_id = subscription_id + self.subscription_id: str = "UNSET" + + async def _async_setup(self) -> None: + """Obtain subscription ID in setup phase.""" + try: + sub_details = await self.client.get_subscription_details() + except (TransportServerError, LoginFailedException, ClientResponseError) as err: + raise ConfigEntryError("Authentication failed") from err + + self.subscription_id = sub_details["subscription"]["id"] async def _async_update_data(self) -> MonarchData: """Fetch data for all accounts.""" diff --git a/tests/components/monarchmoney/snapshots/test_sensor.ambr b/tests/components/monarchmoney/snapshots/test_sensor.ambr index 092bcece3961b5..691bb33092282b 100644 --- a/tests/components/monarchmoney/snapshots/test_sensor.ambr +++ b/tests/components/monarchmoney/snapshots/test_sensor.ambr @@ -30,7 +30,7 @@ 'previous_unique_id': None, 'supported_features': 0, 'translation_key': 'sum_expense', - 'unique_id': 'monarch_money_cashflow_sum_expense', + 'unique_id': '222260252323873333_cashflow_sum_expense', 'unit_of_measurement': '$', }) # --- @@ -82,7 +82,7 @@ 'previous_unique_id': None, 'supported_features': 0, 'translation_key': 'sum_income', - 'unique_id': 'monarch_money_cashflow_sum_income', + 'unique_id': '222260252323873333_cashflow_sum_income', 'unit_of_measurement': '$', }) # --- @@ -135,7 +135,7 @@ 'previous_unique_id': None, 'supported_features': 0, 'translation_key': 'savings_rate', - 'unique_id': 'monarch_money_cashflow_savings_rate', + 'unique_id': '222260252323873333_cashflow_savings_rate', 'unit_of_measurement': '%', }) # --- @@ -185,7 +185,7 @@ 'previous_unique_id': None, 'supported_features': 0, 'translation_key': 'savings', - 'unique_id': 'monarch_money_cashflow_savings', + 'unique_id': '222260252323873333_cashflow_savings', 'unit_of_measurement': '$', }) # --- From 92600f95bd2017422dca33bf1e57729cab659516 Mon Sep 17 00:00:00 2001 From: Jeef Date: Mon, 19 Aug 2024 22:34:49 -0600 Subject: [PATCH 22/67] Update homeassistant/components/monarchmoney/config_flow.py Co-authored-by: Joost Lekkerkerker --- homeassistant/components/monarchmoney/config_flow.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/monarchmoney/config_flow.py b/homeassistant/components/monarchmoney/config_flow.py index 7d894f32fa5070..5c25d10959ab63 100644 --- a/homeassistant/components/monarchmoney/config_flow.py +++ b/homeassistant/components/monarchmoney/config_flow.py @@ -81,7 +81,7 @@ async def validate_login( use_saved_session=False, ) except RequireMFAException as err: - raise RequireMFAException from err + raise except LoginFailedException as err: raise InvalidAuth from err From a7985fd8e6a8cb79068d1baa9a25694ae056eeed Mon Sep 17 00:00:00 2001 From: Jeff Stein Date: Mon, 19 Aug 2024 22:43:56 -0600 Subject: [PATCH 23/67] ruff ruff --- .../components/monarchmoney/config_flow.py | 2 +- .../components/monarchmoney/entity.py | 22 ++++++++++++------- .../components/monarchmoney/sensor.py | 4 ++-- 3 files changed, 17 insertions(+), 11 deletions(-) diff --git a/homeassistant/components/monarchmoney/config_flow.py b/homeassistant/components/monarchmoney/config_flow.py index 5c25d10959ab63..b6ec7812491bc4 100644 --- a/homeassistant/components/monarchmoney/config_flow.py +++ b/homeassistant/components/monarchmoney/config_flow.py @@ -80,7 +80,7 @@ async def validate_login( save_session=False, use_saved_session=False, ) - except RequireMFAException as err: + except RequireMFAException: raise except LoginFailedException as err: raise InvalidAuth from err diff --git a/homeassistant/components/monarchmoney/entity.py b/homeassistant/components/monarchmoney/entity.py index 64f11b2add7d47..1faa51b896fdf5 100644 --- a/homeassistant/components/monarchmoney/entity.py +++ b/homeassistant/components/monarchmoney/entity.py @@ -10,11 +10,20 @@ from .coordinator import MonarchMoneyDataUpdateCoordinator -class MonarchMoneyBaseEntity(CoordinatorEntity[MonarchMoneyDataUpdateCoordinator]): - """Custom entity for Cashflow sensors.""" +class MonarchMoneyEntityBase(CoordinatorEntity[MonarchMoneyDataUpdateCoordinator]): + """Base entity for Monarch Money with entity name attribute.""" _attr_has_entity_name = True + def __init__(self, coordinator: MonarchMoneyDataUpdateCoordinator) -> None: + """Initialize the Monarch Money Entity.""" + super().__init__(coordinator) + self.coordinator = coordinator + + +class MonarchMoneyCashFlowEntity(MonarchMoneyEntityBase): + """Custom entity for Cashflow sensors.""" + def __init__( self, coordinator: MonarchMoneyDataUpdateCoordinator, @@ -26,7 +35,6 @@ def __init__( self._attr_unique_id = ( f"{coordinator.subscription_id}_cashflow_{description.key}" ) - self._data = data self.entity_description = description self._attr_device_info = DeviceInfo( @@ -41,11 +49,9 @@ def summary_data(self) -> Any: return self.coordinator.cashflow_summary -class MonarchMoneyAccountEntity(CoordinatorEntity[MonarchMoneyDataUpdateCoordinator]): +class MonarchMoneyAccountEntity(MonarchMoneyEntityBase): """Define a generic class for Entities.""" - _attr_has_entity_name = True - def __init__( self, coordinator: MonarchMoneyDataUpdateCoordinator, @@ -74,14 +80,14 @@ def __init__( if not configuration_url.startswith(("http://", "https://")): configuration_url = f"http://{configuration_url}" - self._attr_unique_id = f"{coordinator.subscription_id}_{account["displayName"]}_{description.translation_key}" + self._attr_unique_id = f"{coordinator.subscription_id}_{account['displayName']}_{description.translation_key}" atype = account["type"]["display"] asubtype = account["subtype"]["display"] self._attr_device_info = DeviceInfo( identifiers={(DOMAIN, account["id"])}, - name=f"{institution} {account["displayName"]}", + name=f"{institution} {account['displayName']}", entry_type=DeviceEntryType.SERVICE, manufacturer=provider, model=f"{institution} - {atype} - {asubtype}", diff --git a/homeassistant/components/monarchmoney/sensor.py b/homeassistant/components/monarchmoney/sensor.py index f98858200dceec..35dd85e0e5d639 100644 --- a/homeassistant/components/monarchmoney/sensor.py +++ b/homeassistant/components/monarchmoney/sensor.py @@ -18,7 +18,7 @@ from . import MonarchMoneyConfigEntry from .const import LOGGER -from .entity import MonarchMoneyAccountEntity, MonarchMoneyBaseEntity +from .entity import MonarchMoneyAccountEntity, MonarchMoneyCashFlowEntity def _type_to_icon(account: Any) -> str: @@ -179,7 +179,7 @@ async def async_setup_entry( async_add_entities(entity_list) -class MonarchMoneyCashFlowSensor(MonarchMoneyBaseEntity, SensorEntity): +class MonarchMoneyCashFlowSensor(MonarchMoneyCashFlowEntity, SensorEntity): """Cashflow summary sensor.""" entity_description: MonarchMoneySensorEntityDescription From 14dc3000c123435e20a2420360a8ef347c96954c Mon Sep 17 00:00:00 2001 From: Jeff Stein Date: Mon, 19 Aug 2024 23:11:22 -0600 Subject: [PATCH 24/67] ruff ruff --- .../components/monarchmoney/coordinator.py | 33 +++++++-- .../components/monarchmoney/sensor.py | 69 ++++++++++++++----- 2 files changed, 80 insertions(+), 22 deletions(-) diff --git a/homeassistant/components/monarchmoney/coordinator.py b/homeassistant/components/monarchmoney/coordinator.py index d837973b2e4239..4d163154089c5c 100644 --- a/homeassistant/components/monarchmoney/coordinator.py +++ b/homeassistant/components/monarchmoney/coordinator.py @@ -75,9 +75,30 @@ def accounts(self) -> list[dict[str, Any]]: return self.data.account_data - def get_account_for_id(self, account_id: str) -> dict[str, Any] | None: - """Get account for id.""" - for account in self.data.account_data: - if account["id"] == account_id: - return account - return None + @property + def value_accounts(self) -> list[dict[str, Any]]: + """Return value accounts.""" + return [ + x + for x in self.accounts + if x["type"]["name"] + in ["real-estate", "vehicle", "valuables", "other_assets"] + ] + + @property + def balance_accounts(self) -> list[dict[str, Any]]: + """Return accounts that aren't assets.""" + return [ + x + for x in self.accounts + if x["type"]["name"] + not in ["real-estate", "vehicle", "valuables", "other_assets"] + ] + + +def get_account_for_id(self, account_id: str) -> dict[str, Any] | None: + """Get account for id.""" + for account in self.data.account_data: + if account["id"] == account_id: + return account + return None diff --git a/homeassistant/components/monarchmoney/sensor.py b/homeassistant/components/monarchmoney/sensor.py index 35dd85e0e5d639..65b4b338455fe6 100644 --- a/homeassistant/components/monarchmoney/sensor.py +++ b/homeassistant/components/monarchmoney/sensor.py @@ -85,6 +85,19 @@ class MonarchMoneySensorEntityDescription(SensorEntityDescription): icon_fn: Callable[[Any], str] | None = None +MONARCH_MONEY_VALUE_SENSORS: tuple[MonarchMoneySensorEntityDescription, ...] = ( + MonarchMoneySensorEntityDescription( + key="value", + translation_key="value", + state_class=SensorStateClass.TOTAL, + device_class=SensorDeviceClass.MONETARY, + value_fn=lambda account: account["currentBalance"], + picture_fn=lambda account: account["logoUrl"], + icon_fn=_type_to_icon, + native_unit_of_measurement=CURRENCY_DOLLAR, + ), +) + MONARCH_MONEY_SENSORS: tuple[MonarchMoneySensorEntityDescription, ...] = ( MonarchMoneySensorEntityDescription( key="currentBalance", @@ -96,6 +109,9 @@ class MonarchMoneySensorEntityDescription(SensorEntityDescription): icon_fn=_type_to_icon, native_unit_of_measurement=CURRENCY_DOLLAR, ), +) + +MONARCH_MONEY_AGE_SENSORS: tuple[MonarchMoneySensorEntityDescription, ...] = ( MonarchMoneySensorEntityDescription( key="age", translation_key="age", @@ -159,22 +175,43 @@ async def async_setup_entry( """Set up Monarch Money sensors for config entries.""" mm_coordinator = config_entry.runtime_data - entity_list = [ - MonarchMoneyCashFlowSensor( - mm_coordinator, - sensor_description, - mm_coordinator.cashflow_summary, - ) - for sensor_description in MONARCH_CASHFLOW_SENSORS - ] + [ - MonarchMoneySensor( - mm_coordinator, - sensor_description, - account, - ) - for account in mm_coordinator.accounts - for sensor_description in MONARCH_MONEY_SENSORS - ] + entity_list = ( + [ + MonarchMoneyCashFlowSensor( + mm_coordinator, + sensor_description, + mm_coordinator.cashflow_summary, + ) + for sensor_description in MONARCH_CASHFLOW_SENSORS + ] + + [ + MonarchMoneySensor( + mm_coordinator, + sensor_description, + account, + ) + for account in mm_coordinator.balance_accounts + for sensor_description in MONARCH_MONEY_SENSORS + ] + + [ + MonarchMoneySensor( + mm_coordinator, + sensor_description, + account, + ) + for account in mm_coordinator.accounts + for sensor_description in MONARCH_MONEY_AGE_SENSORS + ] + + [ + MonarchMoneySensor( + mm_coordinator, + sensor_description, + account, + ) + for account in mm_coordinator.value_accounts + for sensor_description in MONARCH_MONEY_VALUE_SENSORS + ] + ) async_add_entities(entity_list) From 157e859e3fa5afdacffef467946a753bc155824a Mon Sep 17 00:00:00 2001 From: Jeff Stein Date: Mon, 19 Aug 2024 23:23:06 -0600 Subject: [PATCH 25/67] split ot value and balance sensors... need to go tos leep --- .../components/monarchmoney/coordinator.py | 19 +++--- .../components/monarchmoney/strings.json | 2 + .../monarchmoney/snapshots/test_sensor.ambr | 58 ++++++++++++++++++- 3 files changed, 68 insertions(+), 11 deletions(-) diff --git a/homeassistant/components/monarchmoney/coordinator.py b/homeassistant/components/monarchmoney/coordinator.py index 4d163154089c5c..5ce2992a8593d9 100644 --- a/homeassistant/components/monarchmoney/coordinator.py +++ b/homeassistant/components/monarchmoney/coordinator.py @@ -1,5 +1,6 @@ """Data coordinator for monarch money.""" +import asyncio from dataclasses import dataclass from datetime import timedelta from typing import Any @@ -56,8 +57,9 @@ async def _async_setup(self) -> None: async def _async_update_data(self) -> MonarchData: """Fetch data for all accounts.""" - account_data = await self.client.get_accounts() - cashflow_summary = await self.client.get_cashflow_summary() + account_data, cashflow_summary = await asyncio.gather( + self.client.get_accounts(), self.client.get_cashflow_summary() + ) return MonarchData( account_data=account_data["accounts"], @@ -95,10 +97,9 @@ def balance_accounts(self) -> list[dict[str, Any]]: not in ["real-estate", "vehicle", "valuables", "other_assets"] ] - -def get_account_for_id(self, account_id: str) -> dict[str, Any] | None: - """Get account for id.""" - for account in self.data.account_data: - if account["id"] == account_id: - return account - return None + def get_account_for_id(self, account_id: str) -> dict[str, Any] | None: + """Get account for id.""" + for account in self.data.account_data: + if account["id"] == account_id: + return account + return None diff --git a/homeassistant/components/monarchmoney/strings.json b/homeassistant/components/monarchmoney/strings.json index ac2ee0135821ea..2e7b049a33766d 100644 --- a/homeassistant/components/monarchmoney/strings.json +++ b/homeassistant/components/monarchmoney/strings.json @@ -23,6 +23,8 @@ "entity": { "sensor": { "balance": { "name": "Balance" }, + "value": { "name": "Value" }, + "age": { "name": "Data age" }, diff --git a/tests/components/monarchmoney/snapshots/test_sensor.ambr b/tests/components/monarchmoney/snapshots/test_sensor.ambr index 691bb33092282b..2007c5e9145e95 100644 --- a/tests/components/monarchmoney/snapshots/test_sensor.ambr +++ b/tests/components/monarchmoney/snapshots/test_sensor.ambr @@ -1285,8 +1285,8 @@ 'platform': 'monarchmoney', 'previous_unique_id': None, 'supported_features': 0, - 'translation_key': 'balance', - 'unique_id': '222260252323873333_2050 Toyota RAV8_balance', + 'translation_key': 'value', + 'unique_id': '222260252323873333_2050 Toyota RAV8_value', 'unit_of_measurement': '$', }) # --- @@ -1405,6 +1405,60 @@ 'state': '2024-08-16T17:37:21+00:00', }) # --- +# name: test_all_entities[sensor.vinaudit_2050_toyota_rav8_value-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.vinaudit_2050_toyota_rav8_value', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': 'mdi:car', + 'original_name': 'Value', + 'platform': 'monarchmoney', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'value', + 'unique_id': '222260252323873333_2050 Toyota RAV8_value', + 'unit_of_measurement': '$', + }) +# --- +# name: test_all_entities[sensor.vinaudit_2050_toyota_rav8_value-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'attribution': 'Data provided by Monarch Money API via vin_audit', + 'device_class': 'monetary', + 'entity_picture': 'https://api.monarchmoney.com/cdn-cgi/image/width=128/images/institution/159427559853802644', + 'friendly_name': 'VinAudit 2050 Toyota RAV8 Value', + 'icon': 'mdi:car', + 'state_class': , + 'unit_of_measurement': '$', + }), + 'context': , + 'entity_id': 'sensor.vinaudit_2050_toyota_rav8_value', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '11075.58', + }) +# --- # name: test_all_entities[sensor.zillow_house_balance-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ From e9149a49ac5c94a27f3c169553bde8a46a94483c Mon Sep 17 00:00:00 2001 From: Jeff Stein Date: Tue, 20 Aug 2024 09:36:12 -0600 Subject: [PATCH 26/67] removed icons --- .../components/monarchmoney/coordinator.py | 85 +- .../components/monarchmoney/entity.py | 32 +- .../components/monarchmoney/sensor.py | 80 +- .../monarchmoney/snapshots/test_sensor.ambr | 1201 ++++++++++++++++- 4 files changed, 1280 insertions(+), 118 deletions(-) diff --git a/homeassistant/components/monarchmoney/coordinator.py b/homeassistant/components/monarchmoney/coordinator.py index 5ce2992a8593d9..8b67c91114cc21 100644 --- a/homeassistant/components/monarchmoney/coordinator.py +++ b/homeassistant/components/monarchmoney/coordinator.py @@ -2,7 +2,7 @@ import asyncio from dataclasses import dataclass -from datetime import timedelta +from datetime import datetime, timedelta from typing import Any from aiohttp import ClientResponseError @@ -17,14 +17,65 @@ from .const import LOGGER +@dataclass +class MonarchAccount: + """Dataclass to store & parse account data from monarch accounts.""" + + id: str + logo_url: str | None + name: str + balance: float + type: str # type will be used for icons + type_name: str # type name will be used for device + subtype: str + subtype_name: str + data_provider: str + institution_url: str | None + institution_name: str | None + last_update: datetime + date_created: datetime + + @property + def is_value_account(self): + """Return true if we are tracking a value type asset.""" + return self.type in ["real-estate", "vehicle", "valuables", "other_assets"] + + @property + def is_balance_account(self): + """Whether to show a balance sensor or a value sensor.""" + return not self.is_value_account + + @dataclass class MonarchData: """Data class to hold monarch data.""" - account_data: list[dict[str, Any]] + account_data: list[MonarchAccount] cashflow_summary: dict[str, Any] +def _build_monarch_account(data: dict[str, Any]) -> MonarchAccount: + """Build a monarch account object.""" + institution = data.get("institution") or {} + credential = data.get("credential") or {} + + return MonarchAccount( + id=data["id"], + logo_url=data.get("logoUrl"), + name=data["displayName"], + balance=data["currentBalance"], + type=data["type"]["name"], + type_name=data["type"]["display"], + subtype=data["subtype"]["name"], + subtype_name=data["subtype"]["display"], + data_provider=credential.get("dataProvider", "Manual entry"), + last_update=datetime.fromisoformat(data["updatedAt"]), + date_created=datetime.fromisoformat(data["createdAt"]), + institution_url=institution.get("url", None), + institution_name=institution.get("name", "Manual entry"), + ) + + class MonarchMoneyDataUpdateCoordinator(DataUpdateCoordinator[MonarchData]): """Data update coordinator for Monarch Money.""" @@ -62,7 +113,9 @@ async def _async_update_data(self) -> MonarchData: ) return MonarchData( - account_data=account_data["accounts"], + account_data=[ + _build_monarch_account(acc) for acc in account_data["accounts"] + ], cashflow_summary=cashflow_summary["summary"][0]["summary"], ) @@ -72,34 +125,24 @@ def cashflow_summary(self) -> dict[str, Any]: return self.data.cashflow_summary @property - def accounts(self) -> list[dict[str, Any]]: + def accounts(self) -> list[MonarchAccount]: """Return accounts.""" return self.data.account_data @property - def value_accounts(self) -> list[dict[str, Any]]: + def value_accounts(self) -> list[MonarchAccount]: """Return value accounts.""" - return [ - x - for x in self.accounts - if x["type"]["name"] - in ["real-estate", "vehicle", "valuables", "other_assets"] - ] + return [x for x in self.accounts if x.is_value_account] @property - def balance_accounts(self) -> list[dict[str, Any]]: + def balance_accounts(self) -> list[MonarchAccount]: """Return accounts that aren't assets.""" - return [ - x - for x in self.accounts - if x["type"]["name"] - not in ["real-estate", "vehicle", "valuables", "other_assets"] - ] - - def get_account_for_id(self, account_id: str) -> dict[str, Any] | None: + return [x for x in self.accounts if x.is_balance_account] + + def get_account_for_id(self, account_id: str) -> MonarchAccount | None: """Get account for id.""" for account in self.data.account_data: - if account["id"] == account_id: + if account.id == account_id: return account return None diff --git a/homeassistant/components/monarchmoney/entity.py b/homeassistant/components/monarchmoney/entity.py index 1faa51b896fdf5..2f2fe5162061cb 100644 --- a/homeassistant/components/monarchmoney/entity.py +++ b/homeassistant/components/monarchmoney/entity.py @@ -7,7 +7,7 @@ from homeassistant.helpers.update_coordinator import CoordinatorEntity from .const import DOMAIN -from .coordinator import MonarchMoneyDataUpdateCoordinator +from .coordinator import MonarchAccount, MonarchMoneyDataUpdateCoordinator class MonarchMoneyEntityBase(CoordinatorEntity[MonarchMoneyDataUpdateCoordinator]): @@ -56,41 +56,35 @@ def __init__( self, coordinator: MonarchMoneyDataUpdateCoordinator, description: EntityDescription, - account: Any, + account: MonarchAccount, ) -> None: """Initialize the Monarch Money Entity.""" super().__init__(coordinator) self.entity_description = description - self._account_id = account["id"] + self._account_id = account.id # Parse out some fields institution = "Manual entry" configuration_url = "http://monarchmoney.com" - if account.get("institution") is not None: - institution = account["institution"].get("name", "Manual entry") - configuration_url = account["institution"]["url"] + if account.institution_url is not None: + configuration_url = account.institution_url - provider = account.get("dataProvider", "Manual input") - if account.get("credential") is not None: - provider = account["credential"].get("dataProvider", provider) - - self._attr_attribution = f"Data provided by Monarch Money API via {provider}" + self._attr_attribution = ( + f"Data provided by Monarch Money API via {account.data_provider}" + ) if not configuration_url.startswith(("http://", "https://")): configuration_url = f"http://{configuration_url}" - self._attr_unique_id = f"{coordinator.subscription_id}_{account['displayName']}_{description.translation_key}" - - atype = account["type"]["display"] - asubtype = account["subtype"]["display"] + self._attr_unique_id = f"{coordinator.subscription_id}_{account.name}_{description.translation_key}" self._attr_device_info = DeviceInfo( - identifiers={(DOMAIN, account["id"])}, - name=f"{institution} {account['displayName']}", + identifiers={(DOMAIN, account.id)}, + name=f"{institution} {account.name}", entry_type=DeviceEntryType.SERVICE, - manufacturer=provider, - model=f"{institution} - {atype} - {asubtype}", + manufacturer=account.data_provider, + model=f"{institution} - {account.type_name} - {account.subtype_name}", configuration_url=configuration_url, suggested_area="Banking/Finance", ) diff --git a/homeassistant/components/monarchmoney/sensor.py b/homeassistant/components/monarchmoney/sensor.py index 65b4b338455fe6..49482ffec7e78c 100644 --- a/homeassistant/components/monarchmoney/sensor.py +++ b/homeassistant/components/monarchmoney/sensor.py @@ -17,72 +17,15 @@ from homeassistant.helpers.typing import StateType from . import MonarchMoneyConfigEntry -from .const import LOGGER from .entity import MonarchMoneyAccountEntity, MonarchMoneyCashFlowEntity -def _type_to_icon(account: Any) -> str: - """Return icon mappings - in the case that an account does not have a "logoURL" set - this is a subset of the 86 possible combos.""" - account_type = account["type"]["name"] - account_subtype = account["subtype"]["name"] - - icon_mapping = { - "brokerage": { - "brokerage": "mdi:chart-line", - "cryptocurrency": "mdi:currency-btc", - "ira": "mdi:bank", - "st_401a": "mdi:chart-bell-curve-cumulative", - "st_403b": "mdi:chart-bell-curve-cumulative", - "st_529": "mdi:school-outline", - }, - "vehicle": { - "car": "mdi:car", - "boat": "mdi:sail-boat", - "motorcycle": "mdi:motorbike", - "snowmobile": "mdi:snowmobile", - "bicycle": "mdi:bicycle", - "other": "mdi:car", - }, - "credit": {"credit_card": "mdi:credit-card"}, - "depository": { - "cash_management": "mdi:cash", - "checking": "mdi:checkbook", - "savings": "mdi:piggy-bank-outline", - "money_market": "mdi:piggy-bank-outline", - }, - "loan": { - "line_of_credit": "mdi:credit-card-plus", - "loan": "mdi:bank-outline", - "mortgage": "mdi:home-city-outline", - }, - } - - default_icons = { - "brokerage": "mdi:chart-line", - "credit": "mdi:credit-card", - "depository": "mdi:cash", - "loan": "mdi:bank-outline", - "vehicle": "mdi:car", - } - if account_subtype not in icon_mapping.get(account_type, {}): - LOGGER.debug( - f"Unknown subtype '{account_subtype}' for account type '{account_type}'" - ) - return default_icons.get(account_type, "mdi:cash") - - account_type_icons = icon_mapping.get(account_type, {}) - return account_type_icons.get( - account_subtype, default_icons.get(account_type, "mdi:help") - ) - - @dataclass(frozen=True, kw_only=True) class MonarchMoneySensorEntityDescription(SensorEntityDescription): """Describe a sensor entity.""" - value_fn: Callable[[Any], StateType | datetime] + value_fn: Callable[[Any], StateType] picture_fn: Callable[[Any], str] | None = None - icon_fn: Callable[[Any], str] | None = None MONARCH_MONEY_VALUE_SENSORS: tuple[MonarchMoneySensorEntityDescription, ...] = ( @@ -91,9 +34,8 @@ class MonarchMoneySensorEntityDescription(SensorEntityDescription): translation_key="value", state_class=SensorStateClass.TOTAL, device_class=SensorDeviceClass.MONETARY, - value_fn=lambda account: account["currentBalance"], - picture_fn=lambda account: account["logoUrl"], - icon_fn=_type_to_icon, + value_fn=lambda account: account.balance, + picture_fn=lambda account: account.logo_url, native_unit_of_measurement=CURRENCY_DOLLAR, ), ) @@ -104,9 +46,8 @@ class MonarchMoneySensorEntityDescription(SensorEntityDescription): translation_key="balance", state_class=SensorStateClass.TOTAL, device_class=SensorDeviceClass.MONETARY, - value_fn=lambda account: account["currentBalance"], - picture_fn=lambda account: account["logoUrl"], - icon_fn=_type_to_icon, + value_fn=lambda account: account.balance, + picture_fn=lambda account: account.logo_url, native_unit_of_measurement=CURRENCY_DOLLAR, ), ) @@ -117,14 +58,14 @@ class MonarchMoneySensorEntityDescription(SensorEntityDescription): translation_key="age", device_class=SensorDeviceClass.TIMESTAMP, entity_category=EntityCategory.DIAGNOSTIC, - value_fn=lambda account: datetime.fromisoformat(account["updatedAt"]), + value_fn=lambda account: account.last_update, ), MonarchMoneySensorEntityDescription( key="created", translation_key="created", device_class=SensorDeviceClass.TIMESTAMP, entity_category=EntityCategory.DIAGNOSTIC, - value_fn=lambda account: datetime.fromisoformat(account["createdAt"]), + value_fn=lambda account: account.date_created, ), ) @@ -243,10 +184,3 @@ def entity_picture(self) -> str | None: if self.entity_description.picture_fn is not None: return self.entity_description.picture_fn(self.account_data) return None - - @property - def icon(self) -> str | None: - """Icon function.""" - if self.entity_description.icon_fn is not None: - return self.entity_description.icon_fn(self.account_data) - return None diff --git a/tests/components/monarchmoney/snapshots/test_sensor.ambr b/tests/components/monarchmoney/snapshots/test_sensor.ambr index 2007c5e9145e95..46f9e28b5aca48 100644 --- a/tests/components/monarchmoney/snapshots/test_sensor.ambr +++ b/tests/components/monarchmoney/snapshots/test_sensor.ambr @@ -206,6 +206,1198 @@ 'state': '6000.0', }) # --- +# name: test_all_entities[sensor.manual_entry_2050_toyota_rav8_data_age-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': , + 'entity_id': 'sensor.manual_entry_2050_toyota_rav8_data_age', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Data age', + 'platform': 'monarchmoney', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'age', + 'unique_id': '222260252323873333_2050 Toyota RAV8_age', + 'unit_of_measurement': None, + }) +# --- +# name: test_all_entities[sensor.manual_entry_2050_toyota_rav8_data_age-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'attribution': 'Data provided by Monarch Money API via Manual entry', + 'device_class': 'timestamp', + 'friendly_name': 'Manual entry 2050 Toyota RAV8 Data age', + }), + 'context': , + 'entity_id': 'sensor.manual_entry_2050_toyota_rav8_data_age', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '2024-08-16T17:37:21+00:00', + }) +# --- +# name: test_all_entities[sensor.manual_entry_2050_toyota_rav8_first_sync-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': , + 'entity_id': 'sensor.manual_entry_2050_toyota_rav8_first_sync', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'First sync', + 'platform': 'monarchmoney', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'created', + 'unique_id': '222260252323873333_2050 Toyota RAV8_created', + 'unit_of_measurement': None, + }) +# --- +# name: test_all_entities[sensor.manual_entry_2050_toyota_rav8_first_sync-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'attribution': 'Data provided by Monarch Money API via Manual entry', + 'device_class': 'timestamp', + 'friendly_name': 'Manual entry 2050 Toyota RAV8 First sync', + }), + 'context': , + 'entity_id': 'sensor.manual_entry_2050_toyota_rav8_first_sync', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '2024-08-16T17:37:21+00:00', + }) +# --- +# name: test_all_entities[sensor.manual_entry_2050_toyota_rav8_value-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.manual_entry_2050_toyota_rav8_value', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Value', + 'platform': 'monarchmoney', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'value', + 'unique_id': '222260252323873333_2050 Toyota RAV8_value', + 'unit_of_measurement': '$', + }) +# --- +# name: test_all_entities[sensor.manual_entry_2050_toyota_rav8_value-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'attribution': 'Data provided by Monarch Money API via Manual entry', + 'device_class': 'monetary', + 'entity_picture': 'https://api.monarchmoney.com/cdn-cgi/image/width=128/images/institution/159427559853802644', + 'friendly_name': 'Manual entry 2050 Toyota RAV8 Value', + 'state_class': , + 'unit_of_measurement': '$', + }), + 'context': , + 'entity_id': 'sensor.manual_entry_2050_toyota_rav8_value', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '11075.58', + }) +# --- +# name: test_all_entities[sensor.manual_entry_401_k_balance-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.manual_entry_401_k_balance', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Balance', + 'platform': 'monarchmoney', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'balance', + 'unique_id': '222260252323873333_401.k_balance', + 'unit_of_measurement': '$', + }) +# --- +# name: test_all_entities[sensor.manual_entry_401_k_balance-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'attribution': 'Data provided by Monarch Money API via FINICITY', + 'device_class': 'monetary', + 'entity_picture': 'data:image/png;base64,base64Nonce', + 'friendly_name': 'Manual entry 401.k Balance', + 'state_class': , + 'unit_of_measurement': '$', + }), + 'context': , + 'entity_id': 'sensor.manual_entry_401_k_balance', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '100000.35', + }) +# --- +# name: test_all_entities[sensor.manual_entry_401_k_data_age-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': , + 'entity_id': 'sensor.manual_entry_401_k_data_age', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Data age', + 'platform': 'monarchmoney', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'age', + 'unique_id': '222260252323873333_401.k_age', + 'unit_of_measurement': None, + }) +# --- +# name: test_all_entities[sensor.manual_entry_401_k_data_age-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'attribution': 'Data provided by Monarch Money API via FINICITY', + 'device_class': 'timestamp', + 'friendly_name': 'Manual entry 401.k Data age', + }), + 'context': , + 'entity_id': 'sensor.manual_entry_401_k_data_age', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '2024-02-17T08:13:10+00:00', + }) +# --- +# name: test_all_entities[sensor.manual_entry_401_k_first_sync-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': , + 'entity_id': 'sensor.manual_entry_401_k_first_sync', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'First sync', + 'platform': 'monarchmoney', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'created', + 'unique_id': '222260252323873333_401.k_created', + 'unit_of_measurement': None, + }) +# --- +# name: test_all_entities[sensor.manual_entry_401_k_first_sync-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'attribution': 'Data provided by Monarch Money API via FINICITY', + 'device_class': 'timestamp', + 'friendly_name': 'Manual entry 401.k First sync', + }), + 'context': , + 'entity_id': 'sensor.manual_entry_401_k_first_sync', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '2021-10-15T01:41:54+00:00', + }) +# --- +# name: test_all_entities[sensor.manual_entry_brokerage_balance-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.manual_entry_brokerage_balance', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Balance', + 'platform': 'monarchmoney', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'balance', + 'unique_id': '222260252323873333_Brokerage_balance', + 'unit_of_measurement': '$', + }) +# --- +# name: test_all_entities[sensor.manual_entry_brokerage_balance-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'attribution': 'Data provided by Monarch Money API via PLAID', + 'device_class': 'monetary', + 'entity_picture': 'base64Nonce', + 'friendly_name': 'Manual entry Brokerage Balance', + 'state_class': , + 'unit_of_measurement': '$', + }), + 'context': , + 'entity_id': 'sensor.manual_entry_brokerage_balance', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '1000.5', + }) +# --- +# name: test_all_entities[sensor.manual_entry_brokerage_data_age-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': , + 'entity_id': 'sensor.manual_entry_brokerage_data_age', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Data age', + 'platform': 'monarchmoney', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'age', + 'unique_id': '222260252323873333_Brokerage_age', + 'unit_of_measurement': None, + }) +# --- +# name: test_all_entities[sensor.manual_entry_brokerage_data_age-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'attribution': 'Data provided by Monarch Money API via PLAID', + 'device_class': 'timestamp', + 'friendly_name': 'Manual entry Brokerage Data age', + }), + 'context': , + 'entity_id': 'sensor.manual_entry_brokerage_data_age', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '2022-05-26T00:56:41+00:00', + }) +# --- +# name: test_all_entities[sensor.manual_entry_brokerage_first_sync-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': , + 'entity_id': 'sensor.manual_entry_brokerage_first_sync', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'First sync', + 'platform': 'monarchmoney', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'created', + 'unique_id': '222260252323873333_Brokerage_created', + 'unit_of_measurement': None, + }) +# --- +# name: test_all_entities[sensor.manual_entry_brokerage_first_sync-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'attribution': 'Data provided by Monarch Money API via PLAID', + 'device_class': 'timestamp', + 'friendly_name': 'Manual entry Brokerage First sync', + }), + 'context': , + 'entity_id': 'sensor.manual_entry_brokerage_first_sync', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '2021-10-15T01:32:33+00:00', + }) +# --- +# name: test_all_entities[sensor.manual_entry_checking_balance-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.manual_entry_checking_balance', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Balance', + 'platform': 'monarchmoney', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'balance', + 'unique_id': '222260252323873333_Checking_balance', + 'unit_of_measurement': '$', + }) +# --- +# name: test_all_entities[sensor.manual_entry_checking_balance-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'attribution': 'Data provided by Monarch Money API via PLAID', + 'device_class': 'monetary', + 'entity_picture': 'data:image/png;base64,base64Nonce', + 'friendly_name': 'Manual entry Checking Balance', + 'state_class': , + 'unit_of_measurement': '$', + }), + 'context': , + 'entity_id': 'sensor.manual_entry_checking_balance', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '1000.02', + }) +# --- +# name: test_all_entities[sensor.manual_entry_checking_data_age-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': , + 'entity_id': 'sensor.manual_entry_checking_data_age', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Data age', + 'platform': 'monarchmoney', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'age', + 'unique_id': '222260252323873333_Checking_age', + 'unit_of_measurement': None, + }) +# --- +# name: test_all_entities[sensor.manual_entry_checking_data_age-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'attribution': 'Data provided by Monarch Money API via PLAID', + 'device_class': 'timestamp', + 'friendly_name': 'Manual entry Checking Data age', + }), + 'context': , + 'entity_id': 'sensor.manual_entry_checking_data_age', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '2024-02-17T11:21:05+00:00', + }) +# --- +# name: test_all_entities[sensor.manual_entry_checking_first_sync-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': , + 'entity_id': 'sensor.manual_entry_checking_first_sync', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'First sync', + 'platform': 'monarchmoney', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'created', + 'unique_id': '222260252323873333_Checking_created', + 'unit_of_measurement': None, + }) +# --- +# name: test_all_entities[sensor.manual_entry_checking_first_sync-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'attribution': 'Data provided by Monarch Money API via PLAID', + 'device_class': 'timestamp', + 'friendly_name': 'Manual entry Checking First sync', + }), + 'context': , + 'entity_id': 'sensor.manual_entry_checking_first_sync', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '2021-10-15T01:32:33+00:00', + }) +# --- +# name: test_all_entities[sensor.manual_entry_credit_card_balance-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.manual_entry_credit_card_balance', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Balance', + 'platform': 'monarchmoney', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'balance', + 'unique_id': '222260252323873333_Credit Card_balance', + 'unit_of_measurement': '$', + }) +# --- +# name: test_all_entities[sensor.manual_entry_credit_card_balance-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'attribution': 'Data provided by Monarch Money API via FINICITY', + 'device_class': 'monetary', + 'entity_picture': 'data:image/png;base64,base64Nonce', + 'friendly_name': 'Manual entry Credit Card Balance', + 'state_class': , + 'unit_of_measurement': '$', + }), + 'context': , + 'entity_id': 'sensor.manual_entry_credit_card_balance', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '-200.0', + }) +# --- +# name: test_all_entities[sensor.manual_entry_credit_card_data_age-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': , + 'entity_id': 'sensor.manual_entry_credit_card_data_age', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Data age', + 'platform': 'monarchmoney', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'age', + 'unique_id': '222260252323873333_Credit Card_age', + 'unit_of_measurement': None, + }) +# --- +# name: test_all_entities[sensor.manual_entry_credit_card_data_age-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'attribution': 'Data provided by Monarch Money API via FINICITY', + 'device_class': 'timestamp', + 'friendly_name': 'Manual entry Credit Card Data age', + }), + 'context': , + 'entity_id': 'sensor.manual_entry_credit_card_data_age', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '2022-12-10T18:17:06+00:00', + }) +# --- +# name: test_all_entities[sensor.manual_entry_credit_card_first_sync-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': , + 'entity_id': 'sensor.manual_entry_credit_card_first_sync', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'First sync', + 'platform': 'monarchmoney', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'created', + 'unique_id': '222260252323873333_Credit Card_created', + 'unit_of_measurement': None, + }) +# --- +# name: test_all_entities[sensor.manual_entry_credit_card_first_sync-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'attribution': 'Data provided by Monarch Money API via FINICITY', + 'device_class': 'timestamp', + 'friendly_name': 'Manual entry Credit Card First sync', + }), + 'context': , + 'entity_id': 'sensor.manual_entry_credit_card_first_sync', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '2021-10-15T01:33:46+00:00', + }) +# --- +# name: test_all_entities[sensor.manual_entry_house_balance-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.manual_entry_house_balance', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Balance', + 'platform': 'monarchmoney', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'balance', + 'unique_id': '222260252323873333_House_balance', + 'unit_of_measurement': '$', + }) +# --- +# name: test_all_entities[sensor.manual_entry_house_balance-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'attribution': 'Data provided by Monarch Money API via Manual entry', + 'device_class': 'monetary', + 'entity_picture': 'data:image/png;base64,base64Nonce', + 'friendly_name': 'Manual entry House Balance', + 'state_class': , + 'unit_of_measurement': '$', + }), + 'context': , + 'entity_id': 'sensor.manual_entry_house_balance', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '123000.0', + }) +# --- +# name: test_all_entities[sensor.manual_entry_house_data_age-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': , + 'entity_id': 'sensor.manual_entry_house_data_age', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Data age', + 'platform': 'monarchmoney', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'age', + 'unique_id': '222260252323873333_House_age', + 'unit_of_measurement': None, + }) +# --- +# name: test_all_entities[sensor.manual_entry_house_data_age-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'attribution': 'Data provided by Monarch Money API via Manual entry', + 'device_class': 'timestamp', + 'friendly_name': 'Manual entry House Data age', + }), + 'context': , + 'entity_id': 'sensor.manual_entry_house_data_age', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '2024-02-12T09:00:25+00:00', + }) +# --- +# name: test_all_entities[sensor.manual_entry_house_first_sync-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': , + 'entity_id': 'sensor.manual_entry_house_first_sync', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'First sync', + 'platform': 'monarchmoney', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'created', + 'unique_id': '222260252323873333_House_created', + 'unit_of_measurement': None, + }) +# --- +# name: test_all_entities[sensor.manual_entry_house_first_sync-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'attribution': 'Data provided by Monarch Money API via Manual entry', + 'device_class': 'timestamp', + 'friendly_name': 'Manual entry House First sync', + }), + 'context': , + 'entity_id': 'sensor.manual_entry_house_first_sync', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '2021-10-15T01:39:29+00:00', + }) +# --- +# name: test_all_entities[sensor.manual_entry_mortgage_balance-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.manual_entry_mortgage_balance', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Balance', + 'platform': 'monarchmoney', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'balance', + 'unique_id': '222260252323873333_Mortgage_balance', + 'unit_of_measurement': '$', + }) +# --- +# name: test_all_entities[sensor.manual_entry_mortgage_balance-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'attribution': 'Data provided by Monarch Money API via PLAID', + 'device_class': 'monetary', + 'entity_picture': 'data:image/png;base64,base64Nonce', + 'friendly_name': 'Manual entry Mortgage Balance', + 'state_class': , + 'unit_of_measurement': '$', + }), + 'context': , + 'entity_id': 'sensor.manual_entry_mortgage_balance', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '0.0', + }) +# --- +# name: test_all_entities[sensor.manual_entry_mortgage_data_age-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': , + 'entity_id': 'sensor.manual_entry_mortgage_data_age', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Data age', + 'platform': 'monarchmoney', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'age', + 'unique_id': '222260252323873333_Mortgage_age', + 'unit_of_measurement': None, + }) +# --- +# name: test_all_entities[sensor.manual_entry_mortgage_data_age-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'attribution': 'Data provided by Monarch Money API via PLAID', + 'device_class': 'timestamp', + 'friendly_name': 'Manual entry Mortgage Data age', + }), + 'context': , + 'entity_id': 'sensor.manual_entry_mortgage_data_age', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '2023-08-16T01:41:36+00:00', + }) +# --- +# name: test_all_entities[sensor.manual_entry_mortgage_first_sync-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': , + 'entity_id': 'sensor.manual_entry_mortgage_first_sync', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'First sync', + 'platform': 'monarchmoney', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'created', + 'unique_id': '222260252323873333_Mortgage_created', + 'unit_of_measurement': None, + }) +# --- +# name: test_all_entities[sensor.manual_entry_mortgage_first_sync-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'attribution': 'Data provided by Monarch Money API via PLAID', + 'device_class': 'timestamp', + 'friendly_name': 'Manual entry Mortgage First sync', + }), + 'context': , + 'entity_id': 'sensor.manual_entry_mortgage_first_sync', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '2021-10-15T01:45:25+00:00', + }) +# --- +# name: test_all_entities[sensor.manual_entry_roth_ira_balance-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.manual_entry_roth_ira_balance', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Balance', + 'platform': 'monarchmoney', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'balance', + 'unique_id': '222260252323873333_Roth IRA_balance', + 'unit_of_measurement': '$', + }) +# --- +# name: test_all_entities[sensor.manual_entry_roth_ira_balance-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'attribution': 'Data provided by Monarch Money API via PLAID', + 'device_class': 'monetary', + 'entity_picture': 'data:image/png;base64,base64Nonce', + 'friendly_name': 'Manual entry Roth IRA Balance', + 'state_class': , + 'unit_of_measurement': '$', + }), + 'context': , + 'entity_id': 'sensor.manual_entry_roth_ira_balance', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '10000.43', + }) +# --- +# name: test_all_entities[sensor.manual_entry_roth_ira_data_age-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': , + 'entity_id': 'sensor.manual_entry_roth_ira_data_age', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Data age', + 'platform': 'monarchmoney', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'age', + 'unique_id': '222260252323873333_Roth IRA_age', + 'unit_of_measurement': None, + }) +# --- +# name: test_all_entities[sensor.manual_entry_roth_ira_data_age-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'attribution': 'Data provided by Monarch Money API via PLAID', + 'device_class': 'timestamp', + 'friendly_name': 'Manual entry Roth IRA Data age', + }), + 'context': , + 'entity_id': 'sensor.manual_entry_roth_ira_data_age', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '2024-02-17T13:32:21+00:00', + }) +# --- +# name: test_all_entities[sensor.manual_entry_roth_ira_first_sync-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': , + 'entity_id': 'sensor.manual_entry_roth_ira_first_sync', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'First sync', + 'platform': 'monarchmoney', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'created', + 'unique_id': '222260252323873333_Roth IRA_created', + 'unit_of_measurement': None, + }) +# --- +# name: test_all_entities[sensor.manual_entry_roth_ira_first_sync-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'attribution': 'Data provided by Monarch Money API via PLAID', + 'device_class': 'timestamp', + 'friendly_name': 'Manual entry Roth IRA First sync', + }), + 'context': , + 'entity_id': 'sensor.manual_entry_roth_ira_first_sync', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '2021-10-15T01:35:59+00:00', + }) +# --- # name: test_all_entities[sensor.manual_entry_wallet_balance-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ @@ -231,7 +1423,7 @@ 'options': dict({ }), 'original_device_class': , - 'original_icon': 'mdi:cash', + 'original_icon': None, 'original_name': 'Balance', 'platform': 'monarchmoney', 'previous_unique_id': None, @@ -244,10 +1436,9 @@ # name: test_all_entities[sensor.manual_entry_wallet_balance-state] StateSnapshot({ 'attributes': ReadOnlyDict({ - 'attribution': 'Data provided by Monarch Money API via ', + 'attribution': 'Data provided by Monarch Money API via Manual entry', 'device_class': 'monetary', 'friendly_name': 'Manual entry Wallet Balance', - 'icon': 'mdi:cash', 'state_class': , 'unit_of_measurement': '$', }), @@ -295,7 +1486,7 @@ # name: test_all_entities[sensor.manual_entry_wallet_data_age-state] StateSnapshot({ 'attributes': ReadOnlyDict({ - 'attribution': 'Data provided by Monarch Money API via ', + 'attribution': 'Data provided by Monarch Money API via Manual entry', 'device_class': 'timestamp', 'friendly_name': 'Manual entry Wallet Data age', }), @@ -343,7 +1534,7 @@ # name: test_all_entities[sensor.manual_entry_wallet_first_sync-state] StateSnapshot({ 'attributes': ReadOnlyDict({ - 'attribution': 'Data provided by Monarch Money API via ', + 'attribution': 'Data provided by Monarch Money API via Manual entry', 'device_class': 'timestamp', 'friendly_name': 'Manual entry Wallet First sync', }), From 0ae48fa9b1772e8908bac4ebbbe96d6f0dd1f863 Mon Sep 17 00:00:00 2001 From: Jeff Stein Date: Tue, 20 Aug 2024 09:58:35 -0600 Subject: [PATCH 27/67] Moved summary into a data class --- .../components/monarchmoney/coordinator.py | 26 ++++++++++++++++--- .../components/monarchmoney/entity.py | 8 ++++-- .../components/monarchmoney/sensor.py | 10 ++++--- 3 files changed, 35 insertions(+), 9 deletions(-) diff --git a/homeassistant/components/monarchmoney/coordinator.py b/homeassistant/components/monarchmoney/coordinator.py index 8b67c91114cc21..a221cde37091ce 100644 --- a/homeassistant/components/monarchmoney/coordinator.py +++ b/homeassistant/components/monarchmoney/coordinator.py @@ -46,12 +46,32 @@ def is_balance_account(self): return not self.is_value_account +@dataclass +class MonarchCashflow: + """Cashflow data class.""" + + income: float + expenses: float + savings: float + savings_rate: float + + @dataclass class MonarchData: """Data class to hold monarch data.""" account_data: list[MonarchAccount] - cashflow_summary: dict[str, Any] + cashflow_summary: MonarchCashflow + + +def _build_cashflow(data: dict[str, Any]) -> MonarchCashflow: + """Build a monarch cashflow object.""" + return MonarchCashflow( + income=data["sumIncome"], + expenses=data["sumExpense"], + savings=data["savings"], + savings_rate=data["savingsRate"], + ) def _build_monarch_account(data: dict[str, Any]) -> MonarchAccount: @@ -116,11 +136,11 @@ async def _async_update_data(self) -> MonarchData: account_data=[ _build_monarch_account(acc) for acc in account_data["accounts"] ], - cashflow_summary=cashflow_summary["summary"][0]["summary"], + cashflow_summary=_build_cashflow(cashflow_summary["summary"][0]["summary"]), ) @property - def cashflow_summary(self) -> dict[str, Any]: + def cashflow_summary(self) -> MonarchCashflow: """Return cashflow summary.""" return self.data.cashflow_summary diff --git a/homeassistant/components/monarchmoney/entity.py b/homeassistant/components/monarchmoney/entity.py index 2f2fe5162061cb..de59d31af578e8 100644 --- a/homeassistant/components/monarchmoney/entity.py +++ b/homeassistant/components/monarchmoney/entity.py @@ -7,7 +7,11 @@ from homeassistant.helpers.update_coordinator import CoordinatorEntity from .const import DOMAIN -from .coordinator import MonarchAccount, MonarchMoneyDataUpdateCoordinator +from .coordinator import ( + MonarchAccount, + MonarchCashflow, + MonarchMoneyDataUpdateCoordinator, +) class MonarchMoneyEntityBase(CoordinatorEntity[MonarchMoneyDataUpdateCoordinator]): @@ -44,7 +48,7 @@ def __init__( ) @property - def summary_data(self) -> Any: + def summary_data(self) -> MonarchCashflow: """Return cashflow summary data.""" return self.coordinator.cashflow_summary diff --git a/homeassistant/components/monarchmoney/sensor.py b/homeassistant/components/monarchmoney/sensor.py index 49482ffec7e78c..1c99e0e3fca443 100644 --- a/homeassistant/components/monarchmoney/sensor.py +++ b/homeassistant/components/monarchmoney/sensor.py @@ -28,6 +28,7 @@ class MonarchMoneySensorEntityDescription(SensorEntityDescription): picture_fn: Callable[[Any], str] | None = None +# These sensors include assets like a boat that might have value MONARCH_MONEY_VALUE_SENSORS: tuple[MonarchMoneySensorEntityDescription, ...] = ( MonarchMoneySensorEntityDescription( key="value", @@ -40,6 +41,7 @@ class MonarchMoneySensorEntityDescription(SensorEntityDescription): ), ) +# Most accounts are balance sensors MONARCH_MONEY_SENSORS: tuple[MonarchMoneySensorEntityDescription, ...] = ( MonarchMoneySensorEntityDescription( key="currentBalance", @@ -73,7 +75,7 @@ class MonarchMoneySensorEntityDescription(SensorEntityDescription): MonarchMoneySensorEntityDescription( key="sum_income", translation_key="sum_income", - value_fn=lambda summary: summary["sumIncome"], + value_fn=lambda summary: summary.income, state_class=SensorStateClass.TOTAL, device_class=SensorDeviceClass.MONETARY, native_unit_of_measurement=CURRENCY_DOLLAR, @@ -82,7 +84,7 @@ class MonarchMoneySensorEntityDescription(SensorEntityDescription): MonarchMoneySensorEntityDescription( key="sum_expense", translation_key="sum_expense", - value_fn=lambda summary: summary["sumExpense"], + value_fn=lambda summary: summary.expenses, state_class=SensorStateClass.TOTAL, device_class=SensorDeviceClass.MONETARY, native_unit_of_measurement=CURRENCY_DOLLAR, @@ -91,7 +93,7 @@ class MonarchMoneySensorEntityDescription(SensorEntityDescription): MonarchMoneySensorEntityDescription( key="savings", translation_key="savings", - value_fn=lambda summary: summary["savings"], + value_fn=lambda summary: summary.savings, state_class=SensorStateClass.TOTAL, device_class=SensorDeviceClass.MONETARY, native_unit_of_measurement=CURRENCY_DOLLAR, @@ -100,7 +102,7 @@ class MonarchMoneySensorEntityDescription(SensorEntityDescription): MonarchMoneySensorEntityDescription( key="savings_rate", translation_key="savings_rate", - value_fn=lambda summary: summary["savingsRate"] * 100, + value_fn=lambda summary: summary.savings_rate * 100, suggested_display_precision=1, native_unit_of_measurement=PERCENTAGE, icon="mdi:cash-sync", From 2121e732e739a2225548b1120042b7a25212955d Mon Sep 17 00:00:00 2001 From: Jeff Stein Date: Tue, 20 Aug 2024 10:39:47 -0600 Subject: [PATCH 28/67] efficenty increase --- .../components/monarchmoney/sensor.py | 27 ++++++++++--------- 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/homeassistant/components/monarchmoney/sensor.py b/homeassistant/components/monarchmoney/sensor.py index 1c99e0e3fca443..ede47050d65fc4 100644 --- a/homeassistant/components/monarchmoney/sensor.py +++ b/homeassistant/components/monarchmoney/sensor.py @@ -117,17 +117,16 @@ async def async_setup_entry( ) -> None: """Set up Monarch Money sensors for config entries.""" mm_coordinator = config_entry.runtime_data - - entity_list = ( + entity_list: list[MonarchMoneySensor | MonarchMoneyCashFlowSensor] = [ + MonarchMoneyCashFlowSensor( + mm_coordinator, + sensor_description, + mm_coordinator.cashflow_summary, + ) + for sensor_description in MONARCH_CASHFLOW_SENSORS + ] + entity_list.extend( [ - MonarchMoneyCashFlowSensor( - mm_coordinator, - sensor_description, - mm_coordinator.cashflow_summary, - ) - for sensor_description in MONARCH_CASHFLOW_SENSORS - ] - + [ MonarchMoneySensor( mm_coordinator, sensor_description, @@ -136,7 +135,9 @@ async def async_setup_entry( for account in mm_coordinator.balance_accounts for sensor_description in MONARCH_MONEY_SENSORS ] - + [ + ) + entity_list.extend( + [ MonarchMoneySensor( mm_coordinator, sensor_description, @@ -145,7 +146,9 @@ async def async_setup_entry( for account in mm_coordinator.accounts for sensor_description in MONARCH_MONEY_AGE_SENSORS ] - + [ + ) + entity_list.extend( + [ MonarchMoneySensor( mm_coordinator, sensor_description, From 314cfbc899e0e118fd27ec0195d3bded3f924e70 Mon Sep 17 00:00:00 2001 From: Jeef Date: Tue, 20 Aug 2024 13:14:41 -0600 Subject: [PATCH 29/67] Update homeassistant/components/monarchmoney/coordinator.py Co-authored-by: Joost Lekkerkerker --- homeassistant/components/monarchmoney/coordinator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/monarchmoney/coordinator.py b/homeassistant/components/monarchmoney/coordinator.py index a221cde37091ce..dea9939a678896 100644 --- a/homeassistant/components/monarchmoney/coordinator.py +++ b/homeassistant/components/monarchmoney/coordinator.py @@ -113,7 +113,7 @@ def __init__( name="monarchmoney", update_interval=timedelta(hours=4), ) - self.client: MonarchMoney = client + self.client = client self.subscription_id: str = "UNSET" async def _async_setup(self) -> None: From 68e51fb12f1d8a935ab516f6812efe100ea6dd63 Mon Sep 17 00:00:00 2001 From: Jeef Date: Tue, 20 Aug 2024 13:14:54 -0600 Subject: [PATCH 30/67] Update homeassistant/components/monarchmoney/coordinator.py Co-authored-by: Joost Lekkerkerker --- homeassistant/components/monarchmoney/coordinator.py | 1 + 1 file changed, 1 insertion(+) diff --git a/homeassistant/components/monarchmoney/coordinator.py b/homeassistant/components/monarchmoney/coordinator.py index dea9939a678896..15ea5f727a9433 100644 --- a/homeassistant/components/monarchmoney/coordinator.py +++ b/homeassistant/components/monarchmoney/coordinator.py @@ -100,6 +100,7 @@ class MonarchMoneyDataUpdateCoordinator(DataUpdateCoordinator[MonarchData]): """Data update coordinator for Monarch Money.""" config_entry: ConfigEntry + subscription_id: str def __init__( self, From 4f3502d44842da2de07a1be62c1676c2e8d1a0cc Mon Sep 17 00:00:00 2001 From: Jeef Date: Tue, 20 Aug 2024 13:17:25 -0600 Subject: [PATCH 31/67] Update homeassistant/components/monarchmoney/coordinator.py Co-authored-by: Joost Lekkerkerker --- homeassistant/components/monarchmoney/coordinator.py | 1 - 1 file changed, 1 deletion(-) diff --git a/homeassistant/components/monarchmoney/coordinator.py b/homeassistant/components/monarchmoney/coordinator.py index 15ea5f727a9433..2594587430a014 100644 --- a/homeassistant/components/monarchmoney/coordinator.py +++ b/homeassistant/components/monarchmoney/coordinator.py @@ -115,7 +115,6 @@ def __init__( update_interval=timedelta(hours=4), ) self.client = client - self.subscription_id: str = "UNSET" async def _async_setup(self) -> None: """Obtain subscription ID in setup phase.""" From 069b10d818f3b322bc827c3717b2211f6eccff71 Mon Sep 17 00:00:00 2001 From: Jeef Date: Tue, 20 Aug 2024 13:30:16 -0600 Subject: [PATCH 32/67] Update homeassistant/components/monarchmoney/entity.py Co-authored-by: Joost Lekkerkerker --- homeassistant/components/monarchmoney/entity.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/homeassistant/components/monarchmoney/entity.py b/homeassistant/components/monarchmoney/entity.py index de59d31af578e8..f77f0f1b39fe5a 100644 --- a/homeassistant/components/monarchmoney/entity.py +++ b/homeassistant/components/monarchmoney/entity.py @@ -19,10 +19,6 @@ class MonarchMoneyEntityBase(CoordinatorEntity[MonarchMoneyDataUpdateCoordinator _attr_has_entity_name = True - def __init__(self, coordinator: MonarchMoneyDataUpdateCoordinator) -> None: - """Initialize the Monarch Money Entity.""" - super().__init__(coordinator) - self.coordinator = coordinator class MonarchMoneyCashFlowEntity(MonarchMoneyEntityBase): From 0796628da84d4c51a4a908500d08a4a5f0ba8c76 Mon Sep 17 00:00:00 2001 From: Jeff Stein Date: Tue, 20 Aug 2024 14:03:59 -0600 Subject: [PATCH 33/67] refactor continues --- .../components/monarchmoney/coordinator.py | 4 ++-- .../components/monarchmoney/entity.py | 12 ++++------ .../components/monarchmoney/sensor.py | 23 ++++++++++++------- 3 files changed, 22 insertions(+), 17 deletions(-) diff --git a/homeassistant/components/monarchmoney/coordinator.py b/homeassistant/components/monarchmoney/coordinator.py index 2594587430a014..dfbd979252ab23 100644 --- a/homeassistant/components/monarchmoney/coordinator.py +++ b/homeassistant/components/monarchmoney/coordinator.py @@ -160,9 +160,9 @@ def balance_accounts(self) -> list[MonarchAccount]: """Return accounts that aren't assets.""" return [x for x in self.accounts if x.is_balance_account] - def get_account_for_id(self, account_id: str) -> MonarchAccount | None: + def get_account_for_id(self, account_id: str) -> MonarchAccount: """Get account for id.""" for account in self.data.account_data: if account.id == account_id: return account - return None + raise ValueError(f"Account with id {account_id} not found") diff --git a/homeassistant/components/monarchmoney/entity.py b/homeassistant/components/monarchmoney/entity.py index f77f0f1b39fe5a..79a64073727683 100644 --- a/homeassistant/components/monarchmoney/entity.py +++ b/homeassistant/components/monarchmoney/entity.py @@ -1,7 +1,5 @@ """Monarch money entity definition.""" -from typing import Any - from homeassistant.helpers.device_registry import DeviceEntryType, DeviceInfo from homeassistant.helpers.entity import EntityDescription from homeassistant.helpers.update_coordinator import CoordinatorEntity @@ -20,7 +18,6 @@ class MonarchMoneyEntityBase(CoordinatorEntity[MonarchMoneyDataUpdateCoordinator _attr_has_entity_name = True - class MonarchMoneyCashFlowEntity(MonarchMoneyEntityBase): """Custom entity for Cashflow sensors.""" @@ -28,14 +25,12 @@ def __init__( self, coordinator: MonarchMoneyDataUpdateCoordinator, description: EntityDescription, - data: Any, ) -> None: """Initialize the Monarch Money Entity.""" super().__init__(coordinator) self._attr_unique_id = ( f"{coordinator.subscription_id}_cashflow_{description.key}" ) - self._data = data self.entity_description = description self._attr_device_info = DeviceInfo( identifiers={(DOMAIN, "monarch_money_cashflow")}, @@ -90,6 +85,9 @@ def __init__( ) @property - def account_data(self) -> Any: + def account_data(self) -> MonarchAccount: """Return the account data.""" - return self.coordinator.get_account_for_id(self._account_id) + if account := self.coordinator.get_account_for_id(self._account_id): + return account + + raise ValueError("Account not found") diff --git a/homeassistant/components/monarchmoney/sensor.py b/homeassistant/components/monarchmoney/sensor.py index ede47050d65fc4..c98fb2f5e9b935 100644 --- a/homeassistant/components/monarchmoney/sensor.py +++ b/homeassistant/components/monarchmoney/sensor.py @@ -17,6 +17,7 @@ from homeassistant.helpers.typing import StateType from . import MonarchMoneyConfigEntry +from .coordinator import MonarchAccount, MonarchCashflow from .entity import MonarchMoneyAccountEntity, MonarchMoneyCashFlowEntity @@ -24,7 +25,8 @@ class MonarchMoneySensorEntityDescription(SensorEntityDescription): """Describe a sensor entity.""" - value_fn: Callable[[Any], StateType] + value_fn: Callable[[MonarchAccount], StateType | datetime] | None = None + summary_fn: Callable[[MonarchCashflow], StateType] | None = None picture_fn: Callable[[Any], str] | None = None @@ -75,7 +77,7 @@ class MonarchMoneySensorEntityDescription(SensorEntityDescription): MonarchMoneySensorEntityDescription( key="sum_income", translation_key="sum_income", - value_fn=lambda summary: summary.income, + summary_fn=lambda summary: summary.income, state_class=SensorStateClass.TOTAL, device_class=SensorDeviceClass.MONETARY, native_unit_of_measurement=CURRENCY_DOLLAR, @@ -84,7 +86,7 @@ class MonarchMoneySensorEntityDescription(SensorEntityDescription): MonarchMoneySensorEntityDescription( key="sum_expense", translation_key="sum_expense", - value_fn=lambda summary: summary.expenses, + summary_fn=lambda summary: summary.expenses, state_class=SensorStateClass.TOTAL, device_class=SensorDeviceClass.MONETARY, native_unit_of_measurement=CURRENCY_DOLLAR, @@ -93,7 +95,7 @@ class MonarchMoneySensorEntityDescription(SensorEntityDescription): MonarchMoneySensorEntityDescription( key="savings", translation_key="savings", - value_fn=lambda summary: summary.savings, + summary_fn=lambda summary: summary.savings, state_class=SensorStateClass.TOTAL, device_class=SensorDeviceClass.MONETARY, native_unit_of_measurement=CURRENCY_DOLLAR, @@ -102,7 +104,7 @@ class MonarchMoneySensorEntityDescription(SensorEntityDescription): MonarchMoneySensorEntityDescription( key="savings_rate", translation_key="savings_rate", - value_fn=lambda summary: summary.savings_rate * 100, + summary_fn=lambda summary: summary.savings_rate * 100, suggested_display_precision=1, native_unit_of_measurement=PERCENTAGE, icon="mdi:cash-sync", @@ -117,11 +119,12 @@ async def async_setup_entry( ) -> None: """Set up Monarch Money sensors for config entries.""" mm_coordinator = config_entry.runtime_data + entity_list: list[MonarchMoneySensor | MonarchMoneyCashFlowSensor] = [ MonarchMoneyCashFlowSensor( mm_coordinator, sensor_description, - mm_coordinator.cashflow_summary, + # mm_coordinator.cashflow_summary, ) for sensor_description in MONARCH_CASHFLOW_SENSORS ] @@ -170,7 +173,9 @@ class MonarchMoneyCashFlowSensor(MonarchMoneyCashFlowEntity, SensorEntity): @property def native_value(self) -> StateType | datetime: """Return the state.""" - return self.entity_description.value_fn(self.summary_data) + if self.entity_description.summary_fn is not None: + return self.entity_description.summary_fn(self.summary_data) + return None class MonarchMoneySensor(MonarchMoneyAccountEntity, SensorEntity): @@ -181,7 +186,9 @@ class MonarchMoneySensor(MonarchMoneyAccountEntity, SensorEntity): @property def native_value(self) -> StateType | datetime | None: """Return the state.""" - return self.entity_description.value_fn(self.account_data) + if self.entity_description.value_fn is not None: + return self.entity_description.value_fn(self.account_data) + return None @property def entity_picture(self) -> str | None: From d4bb1fbb1c280f6a8a21801c49562cbedf2b6b96 Mon Sep 17 00:00:00 2001 From: Jeff Stein Date: Tue, 20 Aug 2024 14:07:25 -0600 Subject: [PATCH 34/67] removed a comment --- homeassistant/components/monarchmoney/sensor.py | 1 - 1 file changed, 1 deletion(-) diff --git a/homeassistant/components/monarchmoney/sensor.py b/homeassistant/components/monarchmoney/sensor.py index c98fb2f5e9b935..76a7ca6a96be44 100644 --- a/homeassistant/components/monarchmoney/sensor.py +++ b/homeassistant/components/monarchmoney/sensor.py @@ -124,7 +124,6 @@ async def async_setup_entry( MonarchMoneyCashFlowSensor( mm_coordinator, sensor_description, - # mm_coordinator.cashflow_summary, ) for sensor_description in MONARCH_CASHFLOW_SENSORS ] From b7532cb401bdef3334e8230f6c1d290d1a23a1a4 Mon Sep 17 00:00:00 2001 From: Jeff Stein Date: Tue, 20 Aug 2024 14:28:22 -0600 Subject: [PATCH 35/67] forgot to add a little bit of info --- homeassistant/components/monarchmoney/entity.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/homeassistant/components/monarchmoney/entity.py b/homeassistant/components/monarchmoney/entity.py index 79a64073727683..8adf7ff90b372a 100644 --- a/homeassistant/components/monarchmoney/entity.py +++ b/homeassistant/components/monarchmoney/entity.py @@ -61,6 +61,8 @@ def __init__( # Parse out some fields institution = "Manual entry" + if account.institution_name is not None: + institution = account.institution_name configuration_url = "http://monarchmoney.com" if account.institution_url is not None: configuration_url = account.institution_url From ada4ac231c4bf23bb7d3818650045c6c5b98553b Mon Sep 17 00:00:00 2001 From: Jeff Stein Date: Tue, 20 Aug 2024 14:28:52 -0600 Subject: [PATCH 36/67] updated snapshot --- .../monarchmoney/snapshots/test_sensor.ambr | 36 ++++++++----------- 1 file changed, 14 insertions(+), 22 deletions(-) diff --git a/tests/components/monarchmoney/snapshots/test_sensor.ambr b/tests/components/monarchmoney/snapshots/test_sensor.ambr index 46f9e28b5aca48..a89743054bdf20 100644 --- a/tests/components/monarchmoney/snapshots/test_sensor.ambr +++ b/tests/components/monarchmoney/snapshots/test_sensor.ambr @@ -1571,7 +1571,7 @@ 'options': dict({ }), 'original_device_class': , - 'original_icon': 'mdi:checkbook', + 'original_icon': None, 'original_name': 'Balance', 'platform': 'monarchmoney', 'previous_unique_id': None, @@ -1588,7 +1588,6 @@ 'device_class': 'monetary', 'entity_picture': 'data:image/png;base64,base64Nonce', 'friendly_name': 'Rando Bank Checking Balance', - 'icon': 'mdi:checkbook', 'state_class': , 'unit_of_measurement': '$', }), @@ -1721,7 +1720,7 @@ 'options': dict({ }), 'original_device_class': , - 'original_icon': 'mdi:chart-line', + 'original_icon': None, 'original_name': 'Balance', 'platform': 'monarchmoney', 'previous_unique_id': None, @@ -1738,7 +1737,6 @@ 'device_class': 'monetary', 'entity_picture': 'base64Nonce', 'friendly_name': 'Rando Brokerage Brokerage Balance', - 'icon': 'mdi:chart-line', 'state_class': , 'unit_of_measurement': '$', }), @@ -1871,7 +1869,7 @@ 'options': dict({ }), 'original_device_class': , - 'original_icon': 'mdi:credit-card', + 'original_icon': None, 'original_name': 'Balance', 'platform': 'monarchmoney', 'previous_unique_id': None, @@ -1888,7 +1886,6 @@ 'device_class': 'monetary', 'entity_picture': 'data:image/png;base64,base64Nonce', 'friendly_name': 'Rando Credit Credit Card Balance', - 'icon': 'mdi:credit-card', 'state_class': , 'unit_of_measurement': '$', }), @@ -2021,7 +2018,7 @@ 'options': dict({ }), 'original_device_class': , - 'original_icon': 'mdi:chart-line', + 'original_icon': None, 'original_name': 'Balance', 'platform': 'monarchmoney', 'previous_unique_id': None, @@ -2038,7 +2035,6 @@ 'device_class': 'monetary', 'entity_picture': 'data:image/png;base64,base64Nonce', 'friendly_name': 'Rando Employer Investments 401.k Balance', - 'icon': 'mdi:chart-line', 'state_class': , 'unit_of_measurement': '$', }), @@ -2171,7 +2167,7 @@ 'options': dict({ }), 'original_device_class': , - 'original_icon': 'mdi:chart-line', + 'original_icon': None, 'original_name': 'Balance', 'platform': 'monarchmoney', 'previous_unique_id': None, @@ -2188,7 +2184,6 @@ 'device_class': 'monetary', 'entity_picture': 'data:image/png;base64,base64Nonce', 'friendly_name': 'Rando Investments Roth IRA Balance', - 'icon': 'mdi:chart-line', 'state_class': , 'unit_of_measurement': '$', }), @@ -2321,7 +2316,7 @@ 'options': dict({ }), 'original_device_class': , - 'original_icon': 'mdi:home-city-outline', + 'original_icon': None, 'original_name': 'Balance', 'platform': 'monarchmoney', 'previous_unique_id': None, @@ -2338,7 +2333,6 @@ 'device_class': 'monetary', 'entity_picture': 'data:image/png;base64,base64Nonce', 'friendly_name': 'Rando Mortgage Mortgage Balance', - 'icon': 'mdi:home-city-outline', 'state_class': , 'unit_of_measurement': '$', }), @@ -2536,7 +2530,7 @@ # name: test_all_entities[sensor.vinaudit_2050_toyota_rav8_data_age-state] StateSnapshot({ 'attributes': ReadOnlyDict({ - 'attribution': 'Data provided by Monarch Money API via vin_audit', + 'attribution': 'Data provided by Monarch Money API via Manual entry', 'device_class': 'timestamp', 'friendly_name': 'VinAudit 2050 Toyota RAV8 Data age', }), @@ -2584,7 +2578,7 @@ # name: test_all_entities[sensor.vinaudit_2050_toyota_rav8_first_sync-state] StateSnapshot({ 'attributes': ReadOnlyDict({ - 'attribution': 'Data provided by Monarch Money API via vin_audit', + 'attribution': 'Data provided by Monarch Money API via Manual entry', 'device_class': 'timestamp', 'friendly_name': 'VinAudit 2050 Toyota RAV8 First sync', }), @@ -2621,7 +2615,7 @@ 'options': dict({ }), 'original_device_class': , - 'original_icon': 'mdi:car', + 'original_icon': None, 'original_name': 'Value', 'platform': 'monarchmoney', 'previous_unique_id': None, @@ -2634,11 +2628,10 @@ # name: test_all_entities[sensor.vinaudit_2050_toyota_rav8_value-state] StateSnapshot({ 'attributes': ReadOnlyDict({ - 'attribution': 'Data provided by Monarch Money API via vin_audit', + 'attribution': 'Data provided by Monarch Money API via Manual entry', 'device_class': 'monetary', 'entity_picture': 'https://api.monarchmoney.com/cdn-cgi/image/width=128/images/institution/159427559853802644', 'friendly_name': 'VinAudit 2050 Toyota RAV8 Value', - 'icon': 'mdi:car', 'state_class': , 'unit_of_measurement': '$', }), @@ -2675,7 +2668,7 @@ 'options': dict({ }), 'original_device_class': , - 'original_icon': 'mdi:cash', + 'original_icon': None, 'original_name': 'Balance', 'platform': 'monarchmoney', 'previous_unique_id': None, @@ -2688,11 +2681,10 @@ # name: test_all_entities[sensor.zillow_house_balance-state] StateSnapshot({ 'attributes': ReadOnlyDict({ - 'attribution': 'Data provided by Monarch Money API via zillow', + 'attribution': 'Data provided by Monarch Money API via Manual entry', 'device_class': 'monetary', 'entity_picture': 'data:image/png;base64,base64Nonce', 'friendly_name': 'Zillow House Balance', - 'icon': 'mdi:cash', 'state_class': , 'unit_of_measurement': '$', }), @@ -2740,7 +2732,7 @@ # name: test_all_entities[sensor.zillow_house_data_age-state] StateSnapshot({ 'attributes': ReadOnlyDict({ - 'attribution': 'Data provided by Monarch Money API via zillow', + 'attribution': 'Data provided by Monarch Money API via Manual entry', 'device_class': 'timestamp', 'friendly_name': 'Zillow House Data age', }), @@ -2788,7 +2780,7 @@ # name: test_all_entities[sensor.zillow_house_first_sync-state] StateSnapshot({ 'attributes': ReadOnlyDict({ - 'attribution': 'Data provided by Monarch Money API via zillow', + 'attribution': 'Data provided by Monarch Money API via Manual entry', 'device_class': 'timestamp', 'friendly_name': 'Zillow House First sync', }), From b063db69f401ef2bc8d4dabded80aab370057903 Mon Sep 17 00:00:00 2001 From: Jeff Stein Date: Thu, 5 Sep 2024 16:14:22 -0600 Subject: [PATCH 37/67] Updates to monarch money using the new typed/wrapper setup --- .../components/monarchmoney/__init__.py | 4 +- .../components/monarchmoney/config_flow.py | 5 +- .../components/monarchmoney/coordinator.py | 91 +- .../components/monarchmoney/entity.py | 10 +- .../components/monarchmoney/manifest.json | 2 +- .../components/monarchmoney/sensor.py | 5 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/monarchmoney/conftest.py | 14 +- .../monarchmoney/snapshots/test_sensor.ambr | 2794 ----------------- 10 files changed, 33 insertions(+), 2896 deletions(-) delete mode 100644 tests/components/monarchmoney/snapshots/test_sensor.ambr diff --git a/homeassistant/components/monarchmoney/__init__.py b/homeassistant/components/monarchmoney/__init__.py index a93722e7c2c700..f80b2412e56043 100644 --- a/homeassistant/components/monarchmoney/__init__.py +++ b/homeassistant/components/monarchmoney/__init__.py @@ -2,7 +2,7 @@ from __future__ import annotations -from monarchmoney import MonarchMoney +from monarchmoney_typed import TypedMonarchMoney from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_TOKEN, Platform @@ -19,7 +19,7 @@ async def async_setup_entry( hass: HomeAssistant, entry: MonarchMoneyConfigEntry ) -> bool: """Set up Monarch Money from a config entry.""" - monarch_client = MonarchMoney(token=entry.data.get(CONF_TOKEN)) + monarch_client = TypedMonarchMoney(token=entry.data.get(CONF_TOKEN)) mm_coordinator = MonarchMoneyDataUpdateCoordinator(hass, monarch_client) await mm_coordinator.async_config_entry_first_refresh() diff --git a/homeassistant/components/monarchmoney/config_flow.py b/homeassistant/components/monarchmoney/config_flow.py index b6ec7812491bc4..bc068e736c000e 100644 --- a/homeassistant/components/monarchmoney/config_flow.py +++ b/homeassistant/components/monarchmoney/config_flow.py @@ -5,8 +5,9 @@ import logging from typing import Any -from monarchmoney import LoginFailedException, MonarchMoney, RequireMFAException +from monarchmoney import LoginFailedException, RequireMFAException from monarchmoney.monarchmoney import SESSION_FILE +from monarchmoney_typed import TypedMonarchMoney import voluptuous as vol from homeassistant.config_entries import ConfigFlow, ConfigFlowResult @@ -61,7 +62,7 @@ async def validate_login( email = data[CONF_EMAIL] if not password: password = data[CONF_PASSWORD] - monarch_client = MonarchMoney() + monarch_client = TypedMonarchMoney() if CONF_MFA_CODE in data: mfa_code = data[CONF_MFA_CODE] LOGGER.debug("Attempting to authenticate with MFA code") diff --git a/homeassistant/components/monarchmoney/coordinator.py b/homeassistant/components/monarchmoney/coordinator.py index dfbd979252ab23..2c4aac7c6109da 100644 --- a/homeassistant/components/monarchmoney/coordinator.py +++ b/homeassistant/components/monarchmoney/coordinator.py @@ -2,12 +2,13 @@ import asyncio from dataclasses import dataclass -from datetime import datetime, timedelta -from typing import Any +from datetime import timedelta from aiohttp import ClientResponseError from gql.transport.exceptions import TransportServerError -from monarchmoney import LoginFailedException, MonarchMoney +from monarchmoney import LoginFailedException +from monarchmoney_typed import TypedMonarchMoney +from monarchmoney_typed.models import MonarchAccount, MonarchCashflowSummary from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant @@ -17,83 +18,12 @@ from .const import LOGGER -@dataclass -class MonarchAccount: - """Dataclass to store & parse account data from monarch accounts.""" - - id: str - logo_url: str | None - name: str - balance: float - type: str # type will be used for icons - type_name: str # type name will be used for device - subtype: str - subtype_name: str - data_provider: str - institution_url: str | None - institution_name: str | None - last_update: datetime - date_created: datetime - - @property - def is_value_account(self): - """Return true if we are tracking a value type asset.""" - return self.type in ["real-estate", "vehicle", "valuables", "other_assets"] - - @property - def is_balance_account(self): - """Whether to show a balance sensor or a value sensor.""" - return not self.is_value_account - - -@dataclass -class MonarchCashflow: - """Cashflow data class.""" - - income: float - expenses: float - savings: float - savings_rate: float - - @dataclass class MonarchData: """Data class to hold monarch data.""" account_data: list[MonarchAccount] - cashflow_summary: MonarchCashflow - - -def _build_cashflow(data: dict[str, Any]) -> MonarchCashflow: - """Build a monarch cashflow object.""" - return MonarchCashflow( - income=data["sumIncome"], - expenses=data["sumExpense"], - savings=data["savings"], - savings_rate=data["savingsRate"], - ) - - -def _build_monarch_account(data: dict[str, Any]) -> MonarchAccount: - """Build a monarch account object.""" - institution = data.get("institution") or {} - credential = data.get("credential") or {} - - return MonarchAccount( - id=data["id"], - logo_url=data.get("logoUrl"), - name=data["displayName"], - balance=data["currentBalance"], - type=data["type"]["name"], - type_name=data["type"]["display"], - subtype=data["subtype"]["name"], - subtype_name=data["subtype"]["display"], - data_provider=credential.get("dataProvider", "Manual entry"), - last_update=datetime.fromisoformat(data["updatedAt"]), - date_created=datetime.fromisoformat(data["createdAt"]), - institution_url=institution.get("url", None), - institution_name=institution.get("name", "Manual entry"), - ) + cashflow_summary: MonarchCashflowSummary class MonarchMoneyDataUpdateCoordinator(DataUpdateCoordinator[MonarchData]): @@ -105,7 +35,7 @@ class MonarchMoneyDataUpdateCoordinator(DataUpdateCoordinator[MonarchData]): def __init__( self, hass: HomeAssistant, - client: MonarchMoney, + client: TypedMonarchMoney, ) -> None: """Initialize the coordinator.""" super().__init__( @@ -132,15 +62,10 @@ async def _async_update_data(self) -> MonarchData: self.client.get_accounts(), self.client.get_cashflow_summary() ) - return MonarchData( - account_data=[ - _build_monarch_account(acc) for acc in account_data["accounts"] - ], - cashflow_summary=_build_cashflow(cashflow_summary["summary"][0]["summary"]), - ) + return MonarchData(account_data=account_data, cashflow_summary=cashflow_summary) @property - def cashflow_summary(self) -> MonarchCashflow: + def cashflow_summary(self) -> MonarchCashflowSummary: """Return cashflow summary.""" return self.data.cashflow_summary diff --git a/homeassistant/components/monarchmoney/entity.py b/homeassistant/components/monarchmoney/entity.py index 8adf7ff90b372a..a3a0eb9edfaf83 100644 --- a/homeassistant/components/monarchmoney/entity.py +++ b/homeassistant/components/monarchmoney/entity.py @@ -1,15 +1,13 @@ """Monarch money entity definition.""" +from monarchmoney_typed.models import MonarchAccount, MonarchCashflowSummary + from homeassistant.helpers.device_registry import DeviceEntryType, DeviceInfo from homeassistant.helpers.entity import EntityDescription from homeassistant.helpers.update_coordinator import CoordinatorEntity from .const import DOMAIN -from .coordinator import ( - MonarchAccount, - MonarchCashflow, - MonarchMoneyDataUpdateCoordinator, -) +from .coordinator import MonarchMoneyDataUpdateCoordinator class MonarchMoneyEntityBase(CoordinatorEntity[MonarchMoneyDataUpdateCoordinator]): @@ -39,7 +37,7 @@ def __init__( ) @property - def summary_data(self) -> MonarchCashflow: + def summary_data(self) -> MonarchCashflowSummary: """Return cashflow summary data.""" return self.coordinator.cashflow_summary diff --git a/homeassistant/components/monarchmoney/manifest.json b/homeassistant/components/monarchmoney/manifest.json index 814e0ce5eee427..16afcaafc7ea90 100644 --- a/homeassistant/components/monarchmoney/manifest.json +++ b/homeassistant/components/monarchmoney/manifest.json @@ -5,5 +5,5 @@ "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/monarchmoney", "iot_class": "cloud_polling", - "requirements": ["monarchmoney==0.1.13"] + "requirements": ["monarchmoney-typed==0.1.3"] } diff --git a/homeassistant/components/monarchmoney/sensor.py b/homeassistant/components/monarchmoney/sensor.py index 76a7ca6a96be44..d27d0f3e697dfd 100644 --- a/homeassistant/components/monarchmoney/sensor.py +++ b/homeassistant/components/monarchmoney/sensor.py @@ -5,6 +5,8 @@ from datetime import datetime from typing import Any +from monarchmoney_typed.models import MonarchAccount, MonarchCashflowSummary + from homeassistant.components.sensor import ( SensorDeviceClass, SensorEntity, @@ -17,7 +19,6 @@ from homeassistant.helpers.typing import StateType from . import MonarchMoneyConfigEntry -from .coordinator import MonarchAccount, MonarchCashflow from .entity import MonarchMoneyAccountEntity, MonarchMoneyCashFlowEntity @@ -26,7 +27,7 @@ class MonarchMoneySensorEntityDescription(SensorEntityDescription): """Describe a sensor entity.""" value_fn: Callable[[MonarchAccount], StateType | datetime] | None = None - summary_fn: Callable[[MonarchCashflow], StateType] | None = None + summary_fn: Callable[[MonarchCashflowSummary], StateType] | None = None picture_fn: Callable[[Any], str] | None = None diff --git a/requirements_all.txt b/requirements_all.txt index a0ed435f6475de..f07309b9a903dd 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1369,7 +1369,7 @@ moat-ble==0.1.1 moehlenhoff-alpha2==1.3.1 # homeassistant.components.monarchmoney -monarchmoney==0.1.13 +monarchmoney-typed==0.1.3 # homeassistant.components.monzo monzopy==1.3.2 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index aad61890d55656..acc5dc55ed728d 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1138,7 +1138,7 @@ moat-ble==0.1.1 moehlenhoff-alpha2==1.3.1 # homeassistant.components.monarchmoney -monarchmoney==0.1.13 +monarchmoney-typed==0.1.3 # homeassistant.components.monzo monzopy==1.3.2 diff --git a/tests/components/monarchmoney/conftest.py b/tests/components/monarchmoney/conftest.py index 5b41367def94ed..feb998ae487874 100644 --- a/tests/components/monarchmoney/conftest.py +++ b/tests/components/monarchmoney/conftest.py @@ -5,6 +5,7 @@ from typing import Any from unittest.mock import AsyncMock, PropertyMock, patch +from monarchmoney_typed.models import MonarchAccount, MonarchCashflowSummary import pytest from homeassistant.components.monarchmoney.const import DOMAIN @@ -37,18 +38,23 @@ async def mock_config_entry() -> MockConfigEntry: def mock_config_api() -> Generator[AsyncMock]: """Mock the MonarchMoney class.""" - account_data: dict[str, Any] = json.loads(load_fixture("get_accounts.json", DOMAIN)) - cashflow_summary: dict[str, Any] = json.loads( + account_json: dict[str, Any] = json.loads(load_fixture("get_accounts.json", DOMAIN)) + account_data = [MonarchAccount(data) for data in account_json["accounts"]] + cashflow_json: dict[str, Any] = json.loads( load_fixture("get_cashflow_summary.json", DOMAIN) ) + cashflow_summary = MonarchCashflowSummary(cashflow_json) + subs = json.loads(load_fixture("get_subscription_details.json", DOMAIN)) with ( patch( - "homeassistant.components.monarchmoney.config_flow.MonarchMoney", + "homeassistant.components.monarchmoney.config_flow.TypedMonarchMoney", autospec=True, ) as mock_class, - patch("homeassistant.components.monarchmoney.MonarchMoney", new=mock_class), + patch( + "homeassistant.components.monarchmoney.TypedMonarchMoney", new=mock_class + ), ): instance = mock_class.return_value type(instance).token = PropertyMock(return_value="mocked_token") diff --git a/tests/components/monarchmoney/snapshots/test_sensor.ambr b/tests/components/monarchmoney/snapshots/test_sensor.ambr deleted file mode 100644 index a89743054bdf20..00000000000000 --- a/tests/components/monarchmoney/snapshots/test_sensor.ambr +++ /dev/null @@ -1,2794 +0,0 @@ -# serializer version: 1 -# name: test_all_entities[sensor.cashflow_expense_ytd-entry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': dict({ - 'state_class': , - }), - 'config_entry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'sensor', - 'entity_category': None, - 'entity_id': 'sensor.cashflow_expense_ytd', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'labels': set({ - }), - 'name': None, - 'options': dict({ - }), - 'original_device_class': , - 'original_icon': 'mdi:cash-minus', - 'original_name': 'Expense YTD', - 'platform': 'monarchmoney', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': 'sum_expense', - 'unique_id': '222260252323873333_cashflow_sum_expense', - 'unit_of_measurement': '$', - }) -# --- -# name: test_all_entities[sensor.cashflow_expense_ytd-state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'device_class': 'monetary', - 'friendly_name': 'Cashflow Expense YTD', - 'icon': 'mdi:cash-minus', - 'state_class': , - 'unit_of_measurement': '$', - }), - 'context': , - 'entity_id': 'sensor.cashflow_expense_ytd', - 'last_changed': , - 'last_reported': , - 'last_updated': , - 'state': '-9000.0', - }) -# --- -# name: test_all_entities[sensor.cashflow_income_ytd-entry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': dict({ - 'state_class': , - }), - 'config_entry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'sensor', - 'entity_category': None, - 'entity_id': 'sensor.cashflow_income_ytd', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'labels': set({ - }), - 'name': None, - 'options': dict({ - }), - 'original_device_class': , - 'original_icon': 'mdi:cash-plus', - 'original_name': 'Income YTD', - 'platform': 'monarchmoney', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': 'sum_income', - 'unique_id': '222260252323873333_cashflow_sum_income', - 'unit_of_measurement': '$', - }) -# --- -# name: test_all_entities[sensor.cashflow_income_ytd-state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'device_class': 'monetary', - 'friendly_name': 'Cashflow Income YTD', - 'icon': 'mdi:cash-plus', - 'state_class': , - 'unit_of_measurement': '$', - }), - 'context': , - 'entity_id': 'sensor.cashflow_income_ytd', - 'last_changed': , - 'last_reported': , - 'last_updated': , - 'state': '15000.0', - }) -# --- -# name: test_all_entities[sensor.cashflow_savings_rate-entry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': None, - 'config_entry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'sensor', - 'entity_category': None, - 'entity_id': 'sensor.cashflow_savings_rate', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'labels': set({ - }), - 'name': None, - 'options': dict({ - 'sensor': dict({ - 'suggested_display_precision': 1, - }), - }), - 'original_device_class': None, - 'original_icon': 'mdi:cash-sync', - 'original_name': 'Savings rate', - 'platform': 'monarchmoney', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': 'savings_rate', - 'unique_id': '222260252323873333_cashflow_savings_rate', - 'unit_of_measurement': '%', - }) -# --- -# name: test_all_entities[sensor.cashflow_savings_rate-state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'friendly_name': 'Cashflow Savings rate', - 'icon': 'mdi:cash-sync', - 'unit_of_measurement': '%', - }), - 'context': , - 'entity_id': 'sensor.cashflow_savings_rate', - 'last_changed': , - 'last_reported': , - 'last_updated': , - 'state': '40.0', - }) -# --- -# name: test_all_entities[sensor.cashflow_savings_ytd-entry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': dict({ - 'state_class': , - }), - 'config_entry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'sensor', - 'entity_category': None, - 'entity_id': 'sensor.cashflow_savings_ytd', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'labels': set({ - }), - 'name': None, - 'options': dict({ - }), - 'original_device_class': , - 'original_icon': 'mdi:piggy-bank-outline', - 'original_name': 'Savings YTD', - 'platform': 'monarchmoney', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': 'savings', - 'unique_id': '222260252323873333_cashflow_savings', - 'unit_of_measurement': '$', - }) -# --- -# name: test_all_entities[sensor.cashflow_savings_ytd-state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'device_class': 'monetary', - 'friendly_name': 'Cashflow Savings YTD', - 'icon': 'mdi:piggy-bank-outline', - 'state_class': , - 'unit_of_measurement': '$', - }), - 'context': , - 'entity_id': 'sensor.cashflow_savings_ytd', - 'last_changed': , - 'last_reported': , - 'last_updated': , - 'state': '6000.0', - }) -# --- -# name: test_all_entities[sensor.manual_entry_2050_toyota_rav8_data_age-entry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': None, - 'config_entry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'sensor', - 'entity_category': , - 'entity_id': 'sensor.manual_entry_2050_toyota_rav8_data_age', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'labels': set({ - }), - 'name': None, - 'options': dict({ - }), - 'original_device_class': , - 'original_icon': None, - 'original_name': 'Data age', - 'platform': 'monarchmoney', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': 'age', - 'unique_id': '222260252323873333_2050 Toyota RAV8_age', - 'unit_of_measurement': None, - }) -# --- -# name: test_all_entities[sensor.manual_entry_2050_toyota_rav8_data_age-state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'attribution': 'Data provided by Monarch Money API via Manual entry', - 'device_class': 'timestamp', - 'friendly_name': 'Manual entry 2050 Toyota RAV8 Data age', - }), - 'context': , - 'entity_id': 'sensor.manual_entry_2050_toyota_rav8_data_age', - 'last_changed': , - 'last_reported': , - 'last_updated': , - 'state': '2024-08-16T17:37:21+00:00', - }) -# --- -# name: test_all_entities[sensor.manual_entry_2050_toyota_rav8_first_sync-entry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': None, - 'config_entry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'sensor', - 'entity_category': , - 'entity_id': 'sensor.manual_entry_2050_toyota_rav8_first_sync', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'labels': set({ - }), - 'name': None, - 'options': dict({ - }), - 'original_device_class': , - 'original_icon': None, - 'original_name': 'First sync', - 'platform': 'monarchmoney', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': 'created', - 'unique_id': '222260252323873333_2050 Toyota RAV8_created', - 'unit_of_measurement': None, - }) -# --- -# name: test_all_entities[sensor.manual_entry_2050_toyota_rav8_first_sync-state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'attribution': 'Data provided by Monarch Money API via Manual entry', - 'device_class': 'timestamp', - 'friendly_name': 'Manual entry 2050 Toyota RAV8 First sync', - }), - 'context': , - 'entity_id': 'sensor.manual_entry_2050_toyota_rav8_first_sync', - 'last_changed': , - 'last_reported': , - 'last_updated': , - 'state': '2024-08-16T17:37:21+00:00', - }) -# --- -# name: test_all_entities[sensor.manual_entry_2050_toyota_rav8_value-entry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': dict({ - 'state_class': , - }), - 'config_entry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'sensor', - 'entity_category': None, - 'entity_id': 'sensor.manual_entry_2050_toyota_rav8_value', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'labels': set({ - }), - 'name': None, - 'options': dict({ - }), - 'original_device_class': , - 'original_icon': None, - 'original_name': 'Value', - 'platform': 'monarchmoney', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': 'value', - 'unique_id': '222260252323873333_2050 Toyota RAV8_value', - 'unit_of_measurement': '$', - }) -# --- -# name: test_all_entities[sensor.manual_entry_2050_toyota_rav8_value-state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'attribution': 'Data provided by Monarch Money API via Manual entry', - 'device_class': 'monetary', - 'entity_picture': 'https://api.monarchmoney.com/cdn-cgi/image/width=128/images/institution/159427559853802644', - 'friendly_name': 'Manual entry 2050 Toyota RAV8 Value', - 'state_class': , - 'unit_of_measurement': '$', - }), - 'context': , - 'entity_id': 'sensor.manual_entry_2050_toyota_rav8_value', - 'last_changed': , - 'last_reported': , - 'last_updated': , - 'state': '11075.58', - }) -# --- -# name: test_all_entities[sensor.manual_entry_401_k_balance-entry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': dict({ - 'state_class': , - }), - 'config_entry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'sensor', - 'entity_category': None, - 'entity_id': 'sensor.manual_entry_401_k_balance', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'labels': set({ - }), - 'name': None, - 'options': dict({ - }), - 'original_device_class': , - 'original_icon': None, - 'original_name': 'Balance', - 'platform': 'monarchmoney', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': 'balance', - 'unique_id': '222260252323873333_401.k_balance', - 'unit_of_measurement': '$', - }) -# --- -# name: test_all_entities[sensor.manual_entry_401_k_balance-state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'attribution': 'Data provided by Monarch Money API via FINICITY', - 'device_class': 'monetary', - 'entity_picture': 'data:image/png;base64,base64Nonce', - 'friendly_name': 'Manual entry 401.k Balance', - 'state_class': , - 'unit_of_measurement': '$', - }), - 'context': , - 'entity_id': 'sensor.manual_entry_401_k_balance', - 'last_changed': , - 'last_reported': , - 'last_updated': , - 'state': '100000.35', - }) -# --- -# name: test_all_entities[sensor.manual_entry_401_k_data_age-entry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': None, - 'config_entry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'sensor', - 'entity_category': , - 'entity_id': 'sensor.manual_entry_401_k_data_age', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'labels': set({ - }), - 'name': None, - 'options': dict({ - }), - 'original_device_class': , - 'original_icon': None, - 'original_name': 'Data age', - 'platform': 'monarchmoney', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': 'age', - 'unique_id': '222260252323873333_401.k_age', - 'unit_of_measurement': None, - }) -# --- -# name: test_all_entities[sensor.manual_entry_401_k_data_age-state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'attribution': 'Data provided by Monarch Money API via FINICITY', - 'device_class': 'timestamp', - 'friendly_name': 'Manual entry 401.k Data age', - }), - 'context': , - 'entity_id': 'sensor.manual_entry_401_k_data_age', - 'last_changed': , - 'last_reported': , - 'last_updated': , - 'state': '2024-02-17T08:13:10+00:00', - }) -# --- -# name: test_all_entities[sensor.manual_entry_401_k_first_sync-entry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': None, - 'config_entry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'sensor', - 'entity_category': , - 'entity_id': 'sensor.manual_entry_401_k_first_sync', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'labels': set({ - }), - 'name': None, - 'options': dict({ - }), - 'original_device_class': , - 'original_icon': None, - 'original_name': 'First sync', - 'platform': 'monarchmoney', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': 'created', - 'unique_id': '222260252323873333_401.k_created', - 'unit_of_measurement': None, - }) -# --- -# name: test_all_entities[sensor.manual_entry_401_k_first_sync-state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'attribution': 'Data provided by Monarch Money API via FINICITY', - 'device_class': 'timestamp', - 'friendly_name': 'Manual entry 401.k First sync', - }), - 'context': , - 'entity_id': 'sensor.manual_entry_401_k_first_sync', - 'last_changed': , - 'last_reported': , - 'last_updated': , - 'state': '2021-10-15T01:41:54+00:00', - }) -# --- -# name: test_all_entities[sensor.manual_entry_brokerage_balance-entry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': dict({ - 'state_class': , - }), - 'config_entry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'sensor', - 'entity_category': None, - 'entity_id': 'sensor.manual_entry_brokerage_balance', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'labels': set({ - }), - 'name': None, - 'options': dict({ - }), - 'original_device_class': , - 'original_icon': None, - 'original_name': 'Balance', - 'platform': 'monarchmoney', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': 'balance', - 'unique_id': '222260252323873333_Brokerage_balance', - 'unit_of_measurement': '$', - }) -# --- -# name: test_all_entities[sensor.manual_entry_brokerage_balance-state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'attribution': 'Data provided by Monarch Money API via PLAID', - 'device_class': 'monetary', - 'entity_picture': 'base64Nonce', - 'friendly_name': 'Manual entry Brokerage Balance', - 'state_class': , - 'unit_of_measurement': '$', - }), - 'context': , - 'entity_id': 'sensor.manual_entry_brokerage_balance', - 'last_changed': , - 'last_reported': , - 'last_updated': , - 'state': '1000.5', - }) -# --- -# name: test_all_entities[sensor.manual_entry_brokerage_data_age-entry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': None, - 'config_entry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'sensor', - 'entity_category': , - 'entity_id': 'sensor.manual_entry_brokerage_data_age', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'labels': set({ - }), - 'name': None, - 'options': dict({ - }), - 'original_device_class': , - 'original_icon': None, - 'original_name': 'Data age', - 'platform': 'monarchmoney', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': 'age', - 'unique_id': '222260252323873333_Brokerage_age', - 'unit_of_measurement': None, - }) -# --- -# name: test_all_entities[sensor.manual_entry_brokerage_data_age-state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'attribution': 'Data provided by Monarch Money API via PLAID', - 'device_class': 'timestamp', - 'friendly_name': 'Manual entry Brokerage Data age', - }), - 'context': , - 'entity_id': 'sensor.manual_entry_brokerage_data_age', - 'last_changed': , - 'last_reported': , - 'last_updated': , - 'state': '2022-05-26T00:56:41+00:00', - }) -# --- -# name: test_all_entities[sensor.manual_entry_brokerage_first_sync-entry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': None, - 'config_entry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'sensor', - 'entity_category': , - 'entity_id': 'sensor.manual_entry_brokerage_first_sync', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'labels': set({ - }), - 'name': None, - 'options': dict({ - }), - 'original_device_class': , - 'original_icon': None, - 'original_name': 'First sync', - 'platform': 'monarchmoney', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': 'created', - 'unique_id': '222260252323873333_Brokerage_created', - 'unit_of_measurement': None, - }) -# --- -# name: test_all_entities[sensor.manual_entry_brokerage_first_sync-state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'attribution': 'Data provided by Monarch Money API via PLAID', - 'device_class': 'timestamp', - 'friendly_name': 'Manual entry Brokerage First sync', - }), - 'context': , - 'entity_id': 'sensor.manual_entry_brokerage_first_sync', - 'last_changed': , - 'last_reported': , - 'last_updated': , - 'state': '2021-10-15T01:32:33+00:00', - }) -# --- -# name: test_all_entities[sensor.manual_entry_checking_balance-entry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': dict({ - 'state_class': , - }), - 'config_entry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'sensor', - 'entity_category': None, - 'entity_id': 'sensor.manual_entry_checking_balance', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'labels': set({ - }), - 'name': None, - 'options': dict({ - }), - 'original_device_class': , - 'original_icon': None, - 'original_name': 'Balance', - 'platform': 'monarchmoney', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': 'balance', - 'unique_id': '222260252323873333_Checking_balance', - 'unit_of_measurement': '$', - }) -# --- -# name: test_all_entities[sensor.manual_entry_checking_balance-state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'attribution': 'Data provided by Monarch Money API via PLAID', - 'device_class': 'monetary', - 'entity_picture': 'data:image/png;base64,base64Nonce', - 'friendly_name': 'Manual entry Checking Balance', - 'state_class': , - 'unit_of_measurement': '$', - }), - 'context': , - 'entity_id': 'sensor.manual_entry_checking_balance', - 'last_changed': , - 'last_reported': , - 'last_updated': , - 'state': '1000.02', - }) -# --- -# name: test_all_entities[sensor.manual_entry_checking_data_age-entry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': None, - 'config_entry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'sensor', - 'entity_category': , - 'entity_id': 'sensor.manual_entry_checking_data_age', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'labels': set({ - }), - 'name': None, - 'options': dict({ - }), - 'original_device_class': , - 'original_icon': None, - 'original_name': 'Data age', - 'platform': 'monarchmoney', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': 'age', - 'unique_id': '222260252323873333_Checking_age', - 'unit_of_measurement': None, - }) -# --- -# name: test_all_entities[sensor.manual_entry_checking_data_age-state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'attribution': 'Data provided by Monarch Money API via PLAID', - 'device_class': 'timestamp', - 'friendly_name': 'Manual entry Checking Data age', - }), - 'context': , - 'entity_id': 'sensor.manual_entry_checking_data_age', - 'last_changed': , - 'last_reported': , - 'last_updated': , - 'state': '2024-02-17T11:21:05+00:00', - }) -# --- -# name: test_all_entities[sensor.manual_entry_checking_first_sync-entry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': None, - 'config_entry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'sensor', - 'entity_category': , - 'entity_id': 'sensor.manual_entry_checking_first_sync', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'labels': set({ - }), - 'name': None, - 'options': dict({ - }), - 'original_device_class': , - 'original_icon': None, - 'original_name': 'First sync', - 'platform': 'monarchmoney', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': 'created', - 'unique_id': '222260252323873333_Checking_created', - 'unit_of_measurement': None, - }) -# --- -# name: test_all_entities[sensor.manual_entry_checking_first_sync-state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'attribution': 'Data provided by Monarch Money API via PLAID', - 'device_class': 'timestamp', - 'friendly_name': 'Manual entry Checking First sync', - }), - 'context': , - 'entity_id': 'sensor.manual_entry_checking_first_sync', - 'last_changed': , - 'last_reported': , - 'last_updated': , - 'state': '2021-10-15T01:32:33+00:00', - }) -# --- -# name: test_all_entities[sensor.manual_entry_credit_card_balance-entry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': dict({ - 'state_class': , - }), - 'config_entry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'sensor', - 'entity_category': None, - 'entity_id': 'sensor.manual_entry_credit_card_balance', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'labels': set({ - }), - 'name': None, - 'options': dict({ - }), - 'original_device_class': , - 'original_icon': None, - 'original_name': 'Balance', - 'platform': 'monarchmoney', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': 'balance', - 'unique_id': '222260252323873333_Credit Card_balance', - 'unit_of_measurement': '$', - }) -# --- -# name: test_all_entities[sensor.manual_entry_credit_card_balance-state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'attribution': 'Data provided by Monarch Money API via FINICITY', - 'device_class': 'monetary', - 'entity_picture': 'data:image/png;base64,base64Nonce', - 'friendly_name': 'Manual entry Credit Card Balance', - 'state_class': , - 'unit_of_measurement': '$', - }), - 'context': , - 'entity_id': 'sensor.manual_entry_credit_card_balance', - 'last_changed': , - 'last_reported': , - 'last_updated': , - 'state': '-200.0', - }) -# --- -# name: test_all_entities[sensor.manual_entry_credit_card_data_age-entry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': None, - 'config_entry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'sensor', - 'entity_category': , - 'entity_id': 'sensor.manual_entry_credit_card_data_age', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'labels': set({ - }), - 'name': None, - 'options': dict({ - }), - 'original_device_class': , - 'original_icon': None, - 'original_name': 'Data age', - 'platform': 'monarchmoney', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': 'age', - 'unique_id': '222260252323873333_Credit Card_age', - 'unit_of_measurement': None, - }) -# --- -# name: test_all_entities[sensor.manual_entry_credit_card_data_age-state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'attribution': 'Data provided by Monarch Money API via FINICITY', - 'device_class': 'timestamp', - 'friendly_name': 'Manual entry Credit Card Data age', - }), - 'context': , - 'entity_id': 'sensor.manual_entry_credit_card_data_age', - 'last_changed': , - 'last_reported': , - 'last_updated': , - 'state': '2022-12-10T18:17:06+00:00', - }) -# --- -# name: test_all_entities[sensor.manual_entry_credit_card_first_sync-entry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': None, - 'config_entry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'sensor', - 'entity_category': , - 'entity_id': 'sensor.manual_entry_credit_card_first_sync', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'labels': set({ - }), - 'name': None, - 'options': dict({ - }), - 'original_device_class': , - 'original_icon': None, - 'original_name': 'First sync', - 'platform': 'monarchmoney', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': 'created', - 'unique_id': '222260252323873333_Credit Card_created', - 'unit_of_measurement': None, - }) -# --- -# name: test_all_entities[sensor.manual_entry_credit_card_first_sync-state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'attribution': 'Data provided by Monarch Money API via FINICITY', - 'device_class': 'timestamp', - 'friendly_name': 'Manual entry Credit Card First sync', - }), - 'context': , - 'entity_id': 'sensor.manual_entry_credit_card_first_sync', - 'last_changed': , - 'last_reported': , - 'last_updated': , - 'state': '2021-10-15T01:33:46+00:00', - }) -# --- -# name: test_all_entities[sensor.manual_entry_house_balance-entry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': dict({ - 'state_class': , - }), - 'config_entry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'sensor', - 'entity_category': None, - 'entity_id': 'sensor.manual_entry_house_balance', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'labels': set({ - }), - 'name': None, - 'options': dict({ - }), - 'original_device_class': , - 'original_icon': None, - 'original_name': 'Balance', - 'platform': 'monarchmoney', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': 'balance', - 'unique_id': '222260252323873333_House_balance', - 'unit_of_measurement': '$', - }) -# --- -# name: test_all_entities[sensor.manual_entry_house_balance-state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'attribution': 'Data provided by Monarch Money API via Manual entry', - 'device_class': 'monetary', - 'entity_picture': 'data:image/png;base64,base64Nonce', - 'friendly_name': 'Manual entry House Balance', - 'state_class': , - 'unit_of_measurement': '$', - }), - 'context': , - 'entity_id': 'sensor.manual_entry_house_balance', - 'last_changed': , - 'last_reported': , - 'last_updated': , - 'state': '123000.0', - }) -# --- -# name: test_all_entities[sensor.manual_entry_house_data_age-entry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': None, - 'config_entry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'sensor', - 'entity_category': , - 'entity_id': 'sensor.manual_entry_house_data_age', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'labels': set({ - }), - 'name': None, - 'options': dict({ - }), - 'original_device_class': , - 'original_icon': None, - 'original_name': 'Data age', - 'platform': 'monarchmoney', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': 'age', - 'unique_id': '222260252323873333_House_age', - 'unit_of_measurement': None, - }) -# --- -# name: test_all_entities[sensor.manual_entry_house_data_age-state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'attribution': 'Data provided by Monarch Money API via Manual entry', - 'device_class': 'timestamp', - 'friendly_name': 'Manual entry House Data age', - }), - 'context': , - 'entity_id': 'sensor.manual_entry_house_data_age', - 'last_changed': , - 'last_reported': , - 'last_updated': , - 'state': '2024-02-12T09:00:25+00:00', - }) -# --- -# name: test_all_entities[sensor.manual_entry_house_first_sync-entry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': None, - 'config_entry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'sensor', - 'entity_category': , - 'entity_id': 'sensor.manual_entry_house_first_sync', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'labels': set({ - }), - 'name': None, - 'options': dict({ - }), - 'original_device_class': , - 'original_icon': None, - 'original_name': 'First sync', - 'platform': 'monarchmoney', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': 'created', - 'unique_id': '222260252323873333_House_created', - 'unit_of_measurement': None, - }) -# --- -# name: test_all_entities[sensor.manual_entry_house_first_sync-state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'attribution': 'Data provided by Monarch Money API via Manual entry', - 'device_class': 'timestamp', - 'friendly_name': 'Manual entry House First sync', - }), - 'context': , - 'entity_id': 'sensor.manual_entry_house_first_sync', - 'last_changed': , - 'last_reported': , - 'last_updated': , - 'state': '2021-10-15T01:39:29+00:00', - }) -# --- -# name: test_all_entities[sensor.manual_entry_mortgage_balance-entry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': dict({ - 'state_class': , - }), - 'config_entry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'sensor', - 'entity_category': None, - 'entity_id': 'sensor.manual_entry_mortgage_balance', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'labels': set({ - }), - 'name': None, - 'options': dict({ - }), - 'original_device_class': , - 'original_icon': None, - 'original_name': 'Balance', - 'platform': 'monarchmoney', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': 'balance', - 'unique_id': '222260252323873333_Mortgage_balance', - 'unit_of_measurement': '$', - }) -# --- -# name: test_all_entities[sensor.manual_entry_mortgage_balance-state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'attribution': 'Data provided by Monarch Money API via PLAID', - 'device_class': 'monetary', - 'entity_picture': 'data:image/png;base64,base64Nonce', - 'friendly_name': 'Manual entry Mortgage Balance', - 'state_class': , - 'unit_of_measurement': '$', - }), - 'context': , - 'entity_id': 'sensor.manual_entry_mortgage_balance', - 'last_changed': , - 'last_reported': , - 'last_updated': , - 'state': '0.0', - }) -# --- -# name: test_all_entities[sensor.manual_entry_mortgage_data_age-entry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': None, - 'config_entry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'sensor', - 'entity_category': , - 'entity_id': 'sensor.manual_entry_mortgage_data_age', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'labels': set({ - }), - 'name': None, - 'options': dict({ - }), - 'original_device_class': , - 'original_icon': None, - 'original_name': 'Data age', - 'platform': 'monarchmoney', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': 'age', - 'unique_id': '222260252323873333_Mortgage_age', - 'unit_of_measurement': None, - }) -# --- -# name: test_all_entities[sensor.manual_entry_mortgage_data_age-state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'attribution': 'Data provided by Monarch Money API via PLAID', - 'device_class': 'timestamp', - 'friendly_name': 'Manual entry Mortgage Data age', - }), - 'context': , - 'entity_id': 'sensor.manual_entry_mortgage_data_age', - 'last_changed': , - 'last_reported': , - 'last_updated': , - 'state': '2023-08-16T01:41:36+00:00', - }) -# --- -# name: test_all_entities[sensor.manual_entry_mortgage_first_sync-entry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': None, - 'config_entry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'sensor', - 'entity_category': , - 'entity_id': 'sensor.manual_entry_mortgage_first_sync', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'labels': set({ - }), - 'name': None, - 'options': dict({ - }), - 'original_device_class': , - 'original_icon': None, - 'original_name': 'First sync', - 'platform': 'monarchmoney', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': 'created', - 'unique_id': '222260252323873333_Mortgage_created', - 'unit_of_measurement': None, - }) -# --- -# name: test_all_entities[sensor.manual_entry_mortgage_first_sync-state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'attribution': 'Data provided by Monarch Money API via PLAID', - 'device_class': 'timestamp', - 'friendly_name': 'Manual entry Mortgage First sync', - }), - 'context': , - 'entity_id': 'sensor.manual_entry_mortgage_first_sync', - 'last_changed': , - 'last_reported': , - 'last_updated': , - 'state': '2021-10-15T01:45:25+00:00', - }) -# --- -# name: test_all_entities[sensor.manual_entry_roth_ira_balance-entry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': dict({ - 'state_class': , - }), - 'config_entry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'sensor', - 'entity_category': None, - 'entity_id': 'sensor.manual_entry_roth_ira_balance', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'labels': set({ - }), - 'name': None, - 'options': dict({ - }), - 'original_device_class': , - 'original_icon': None, - 'original_name': 'Balance', - 'platform': 'monarchmoney', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': 'balance', - 'unique_id': '222260252323873333_Roth IRA_balance', - 'unit_of_measurement': '$', - }) -# --- -# name: test_all_entities[sensor.manual_entry_roth_ira_balance-state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'attribution': 'Data provided by Monarch Money API via PLAID', - 'device_class': 'monetary', - 'entity_picture': 'data:image/png;base64,base64Nonce', - 'friendly_name': 'Manual entry Roth IRA Balance', - 'state_class': , - 'unit_of_measurement': '$', - }), - 'context': , - 'entity_id': 'sensor.manual_entry_roth_ira_balance', - 'last_changed': , - 'last_reported': , - 'last_updated': , - 'state': '10000.43', - }) -# --- -# name: test_all_entities[sensor.manual_entry_roth_ira_data_age-entry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': None, - 'config_entry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'sensor', - 'entity_category': , - 'entity_id': 'sensor.manual_entry_roth_ira_data_age', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'labels': set({ - }), - 'name': None, - 'options': dict({ - }), - 'original_device_class': , - 'original_icon': None, - 'original_name': 'Data age', - 'platform': 'monarchmoney', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': 'age', - 'unique_id': '222260252323873333_Roth IRA_age', - 'unit_of_measurement': None, - }) -# --- -# name: test_all_entities[sensor.manual_entry_roth_ira_data_age-state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'attribution': 'Data provided by Monarch Money API via PLAID', - 'device_class': 'timestamp', - 'friendly_name': 'Manual entry Roth IRA Data age', - }), - 'context': , - 'entity_id': 'sensor.manual_entry_roth_ira_data_age', - 'last_changed': , - 'last_reported': , - 'last_updated': , - 'state': '2024-02-17T13:32:21+00:00', - }) -# --- -# name: test_all_entities[sensor.manual_entry_roth_ira_first_sync-entry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': None, - 'config_entry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'sensor', - 'entity_category': , - 'entity_id': 'sensor.manual_entry_roth_ira_first_sync', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'labels': set({ - }), - 'name': None, - 'options': dict({ - }), - 'original_device_class': , - 'original_icon': None, - 'original_name': 'First sync', - 'platform': 'monarchmoney', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': 'created', - 'unique_id': '222260252323873333_Roth IRA_created', - 'unit_of_measurement': None, - }) -# --- -# name: test_all_entities[sensor.manual_entry_roth_ira_first_sync-state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'attribution': 'Data provided by Monarch Money API via PLAID', - 'device_class': 'timestamp', - 'friendly_name': 'Manual entry Roth IRA First sync', - }), - 'context': , - 'entity_id': 'sensor.manual_entry_roth_ira_first_sync', - 'last_changed': , - 'last_reported': , - 'last_updated': , - 'state': '2021-10-15T01:35:59+00:00', - }) -# --- -# name: test_all_entities[sensor.manual_entry_wallet_balance-entry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': dict({ - 'state_class': , - }), - 'config_entry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'sensor', - 'entity_category': None, - 'entity_id': 'sensor.manual_entry_wallet_balance', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'labels': set({ - }), - 'name': None, - 'options': dict({ - }), - 'original_device_class': , - 'original_icon': None, - 'original_name': 'Balance', - 'platform': 'monarchmoney', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': 'balance', - 'unique_id': '222260252323873333_Wallet_balance', - 'unit_of_measurement': '$', - }) -# --- -# name: test_all_entities[sensor.manual_entry_wallet_balance-state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'attribution': 'Data provided by Monarch Money API via Manual entry', - 'device_class': 'monetary', - 'friendly_name': 'Manual entry Wallet Balance', - 'state_class': , - 'unit_of_measurement': '$', - }), - 'context': , - 'entity_id': 'sensor.manual_entry_wallet_balance', - 'last_changed': , - 'last_reported': , - 'last_updated': , - 'state': '20.0', - }) -# --- -# name: test_all_entities[sensor.manual_entry_wallet_data_age-entry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': None, - 'config_entry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'sensor', - 'entity_category': , - 'entity_id': 'sensor.manual_entry_wallet_data_age', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'labels': set({ - }), - 'name': None, - 'options': dict({ - }), - 'original_device_class': , - 'original_icon': None, - 'original_name': 'Data age', - 'platform': 'monarchmoney', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': 'age', - 'unique_id': '222260252323873333_Wallet_age', - 'unit_of_measurement': None, - }) -# --- -# name: test_all_entities[sensor.manual_entry_wallet_data_age-state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'attribution': 'Data provided by Monarch Money API via Manual entry', - 'device_class': 'timestamp', - 'friendly_name': 'Manual entry Wallet Data age', - }), - 'context': , - 'entity_id': 'sensor.manual_entry_wallet_data_age', - 'last_changed': , - 'last_reported': , - 'last_updated': , - 'state': '2024-08-16T14:22:10+00:00', - }) -# --- -# name: test_all_entities[sensor.manual_entry_wallet_first_sync-entry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': None, - 'config_entry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'sensor', - 'entity_category': , - 'entity_id': 'sensor.manual_entry_wallet_first_sync', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'labels': set({ - }), - 'name': None, - 'options': dict({ - }), - 'original_device_class': , - 'original_icon': None, - 'original_name': 'First sync', - 'platform': 'monarchmoney', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': 'created', - 'unique_id': '222260252323873333_Wallet_created', - 'unit_of_measurement': None, - }) -# --- -# name: test_all_entities[sensor.manual_entry_wallet_first_sync-state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'attribution': 'Data provided by Monarch Money API via Manual entry', - 'device_class': 'timestamp', - 'friendly_name': 'Manual entry Wallet First sync', - }), - 'context': , - 'entity_id': 'sensor.manual_entry_wallet_first_sync', - 'last_changed': , - 'last_reported': , - 'last_updated': , - 'state': '2024-08-16T14:22:10+00:00', - }) -# --- -# name: test_all_entities[sensor.rando_bank_checking_balance-entry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': dict({ - 'state_class': , - }), - 'config_entry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'sensor', - 'entity_category': None, - 'entity_id': 'sensor.rando_bank_checking_balance', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'labels': set({ - }), - 'name': None, - 'options': dict({ - }), - 'original_device_class': , - 'original_icon': None, - 'original_name': 'Balance', - 'platform': 'monarchmoney', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': 'balance', - 'unique_id': '222260252323873333_Checking_balance', - 'unit_of_measurement': '$', - }) -# --- -# name: test_all_entities[sensor.rando_bank_checking_balance-state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'attribution': 'Data provided by Monarch Money API via PLAID', - 'device_class': 'monetary', - 'entity_picture': 'data:image/png;base64,base64Nonce', - 'friendly_name': 'Rando Bank Checking Balance', - 'state_class': , - 'unit_of_measurement': '$', - }), - 'context': , - 'entity_id': 'sensor.rando_bank_checking_balance', - 'last_changed': , - 'last_reported': , - 'last_updated': , - 'state': '1000.02', - }) -# --- -# name: test_all_entities[sensor.rando_bank_checking_data_age-entry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': None, - 'config_entry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'sensor', - 'entity_category': , - 'entity_id': 'sensor.rando_bank_checking_data_age', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'labels': set({ - }), - 'name': None, - 'options': dict({ - }), - 'original_device_class': , - 'original_icon': None, - 'original_name': 'Data age', - 'platform': 'monarchmoney', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': 'age', - 'unique_id': '222260252323873333_Checking_age', - 'unit_of_measurement': None, - }) -# --- -# name: test_all_entities[sensor.rando_bank_checking_data_age-state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'attribution': 'Data provided by Monarch Money API via PLAID', - 'device_class': 'timestamp', - 'friendly_name': 'Rando Bank Checking Data age', - }), - 'context': , - 'entity_id': 'sensor.rando_bank_checking_data_age', - 'last_changed': , - 'last_reported': , - 'last_updated': , - 'state': '2024-02-17T11:21:05+00:00', - }) -# --- -# name: test_all_entities[sensor.rando_bank_checking_first_sync-entry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': None, - 'config_entry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'sensor', - 'entity_category': , - 'entity_id': 'sensor.rando_bank_checking_first_sync', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'labels': set({ - }), - 'name': None, - 'options': dict({ - }), - 'original_device_class': , - 'original_icon': None, - 'original_name': 'First sync', - 'platform': 'monarchmoney', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': 'created', - 'unique_id': '222260252323873333_Checking_created', - 'unit_of_measurement': None, - }) -# --- -# name: test_all_entities[sensor.rando_bank_checking_first_sync-state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'attribution': 'Data provided by Monarch Money API via PLAID', - 'device_class': 'timestamp', - 'friendly_name': 'Rando Bank Checking First sync', - }), - 'context': , - 'entity_id': 'sensor.rando_bank_checking_first_sync', - 'last_changed': , - 'last_reported': , - 'last_updated': , - 'state': '2021-10-15T01:32:33+00:00', - }) -# --- -# name: test_all_entities[sensor.rando_brokerage_brokerage_balance-entry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': dict({ - 'state_class': , - }), - 'config_entry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'sensor', - 'entity_category': None, - 'entity_id': 'sensor.rando_brokerage_brokerage_balance', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'labels': set({ - }), - 'name': None, - 'options': dict({ - }), - 'original_device_class': , - 'original_icon': None, - 'original_name': 'Balance', - 'platform': 'monarchmoney', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': 'balance', - 'unique_id': '222260252323873333_Brokerage_balance', - 'unit_of_measurement': '$', - }) -# --- -# name: test_all_entities[sensor.rando_brokerage_brokerage_balance-state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'attribution': 'Data provided by Monarch Money API via PLAID', - 'device_class': 'monetary', - 'entity_picture': 'base64Nonce', - 'friendly_name': 'Rando Brokerage Brokerage Balance', - 'state_class': , - 'unit_of_measurement': '$', - }), - 'context': , - 'entity_id': 'sensor.rando_brokerage_brokerage_balance', - 'last_changed': , - 'last_reported': , - 'last_updated': , - 'state': '1000.5', - }) -# --- -# name: test_all_entities[sensor.rando_brokerage_brokerage_data_age-entry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': None, - 'config_entry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'sensor', - 'entity_category': , - 'entity_id': 'sensor.rando_brokerage_brokerage_data_age', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'labels': set({ - }), - 'name': None, - 'options': dict({ - }), - 'original_device_class': , - 'original_icon': None, - 'original_name': 'Data age', - 'platform': 'monarchmoney', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': 'age', - 'unique_id': '222260252323873333_Brokerage_age', - 'unit_of_measurement': None, - }) -# --- -# name: test_all_entities[sensor.rando_brokerage_brokerage_data_age-state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'attribution': 'Data provided by Monarch Money API via PLAID', - 'device_class': 'timestamp', - 'friendly_name': 'Rando Brokerage Brokerage Data age', - }), - 'context': , - 'entity_id': 'sensor.rando_brokerage_brokerage_data_age', - 'last_changed': , - 'last_reported': , - 'last_updated': , - 'state': '2022-05-26T00:56:41+00:00', - }) -# --- -# name: test_all_entities[sensor.rando_brokerage_brokerage_first_sync-entry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': None, - 'config_entry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'sensor', - 'entity_category': , - 'entity_id': 'sensor.rando_brokerage_brokerage_first_sync', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'labels': set({ - }), - 'name': None, - 'options': dict({ - }), - 'original_device_class': , - 'original_icon': None, - 'original_name': 'First sync', - 'platform': 'monarchmoney', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': 'created', - 'unique_id': '222260252323873333_Brokerage_created', - 'unit_of_measurement': None, - }) -# --- -# name: test_all_entities[sensor.rando_brokerage_brokerage_first_sync-state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'attribution': 'Data provided by Monarch Money API via PLAID', - 'device_class': 'timestamp', - 'friendly_name': 'Rando Brokerage Brokerage First sync', - }), - 'context': , - 'entity_id': 'sensor.rando_brokerage_brokerage_first_sync', - 'last_changed': , - 'last_reported': , - 'last_updated': , - 'state': '2021-10-15T01:32:33+00:00', - }) -# --- -# name: test_all_entities[sensor.rando_credit_credit_card_balance-entry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': dict({ - 'state_class': , - }), - 'config_entry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'sensor', - 'entity_category': None, - 'entity_id': 'sensor.rando_credit_credit_card_balance', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'labels': set({ - }), - 'name': None, - 'options': dict({ - }), - 'original_device_class': , - 'original_icon': None, - 'original_name': 'Balance', - 'platform': 'monarchmoney', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': 'balance', - 'unique_id': '222260252323873333_Credit Card_balance', - 'unit_of_measurement': '$', - }) -# --- -# name: test_all_entities[sensor.rando_credit_credit_card_balance-state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'attribution': 'Data provided by Monarch Money API via FINICITY', - 'device_class': 'monetary', - 'entity_picture': 'data:image/png;base64,base64Nonce', - 'friendly_name': 'Rando Credit Credit Card Balance', - 'state_class': , - 'unit_of_measurement': '$', - }), - 'context': , - 'entity_id': 'sensor.rando_credit_credit_card_balance', - 'last_changed': , - 'last_reported': , - 'last_updated': , - 'state': '-200.0', - }) -# --- -# name: test_all_entities[sensor.rando_credit_credit_card_data_age-entry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': None, - 'config_entry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'sensor', - 'entity_category': , - 'entity_id': 'sensor.rando_credit_credit_card_data_age', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'labels': set({ - }), - 'name': None, - 'options': dict({ - }), - 'original_device_class': , - 'original_icon': None, - 'original_name': 'Data age', - 'platform': 'monarchmoney', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': 'age', - 'unique_id': '222260252323873333_Credit Card_age', - 'unit_of_measurement': None, - }) -# --- -# name: test_all_entities[sensor.rando_credit_credit_card_data_age-state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'attribution': 'Data provided by Monarch Money API via FINICITY', - 'device_class': 'timestamp', - 'friendly_name': 'Rando Credit Credit Card Data age', - }), - 'context': , - 'entity_id': 'sensor.rando_credit_credit_card_data_age', - 'last_changed': , - 'last_reported': , - 'last_updated': , - 'state': '2022-12-10T18:17:06+00:00', - }) -# --- -# name: test_all_entities[sensor.rando_credit_credit_card_first_sync-entry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': None, - 'config_entry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'sensor', - 'entity_category': , - 'entity_id': 'sensor.rando_credit_credit_card_first_sync', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'labels': set({ - }), - 'name': None, - 'options': dict({ - }), - 'original_device_class': , - 'original_icon': None, - 'original_name': 'First sync', - 'platform': 'monarchmoney', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': 'created', - 'unique_id': '222260252323873333_Credit Card_created', - 'unit_of_measurement': None, - }) -# --- -# name: test_all_entities[sensor.rando_credit_credit_card_first_sync-state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'attribution': 'Data provided by Monarch Money API via FINICITY', - 'device_class': 'timestamp', - 'friendly_name': 'Rando Credit Credit Card First sync', - }), - 'context': , - 'entity_id': 'sensor.rando_credit_credit_card_first_sync', - 'last_changed': , - 'last_reported': , - 'last_updated': , - 'state': '2021-10-15T01:33:46+00:00', - }) -# --- -# name: test_all_entities[sensor.rando_employer_investments_401_k_balance-entry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': dict({ - 'state_class': , - }), - 'config_entry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'sensor', - 'entity_category': None, - 'entity_id': 'sensor.rando_employer_investments_401_k_balance', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'labels': set({ - }), - 'name': None, - 'options': dict({ - }), - 'original_device_class': , - 'original_icon': None, - 'original_name': 'Balance', - 'platform': 'monarchmoney', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': 'balance', - 'unique_id': '222260252323873333_401.k_balance', - 'unit_of_measurement': '$', - }) -# --- -# name: test_all_entities[sensor.rando_employer_investments_401_k_balance-state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'attribution': 'Data provided by Monarch Money API via FINICITY', - 'device_class': 'monetary', - 'entity_picture': 'data:image/png;base64,base64Nonce', - 'friendly_name': 'Rando Employer Investments 401.k Balance', - 'state_class': , - 'unit_of_measurement': '$', - }), - 'context': , - 'entity_id': 'sensor.rando_employer_investments_401_k_balance', - 'last_changed': , - 'last_reported': , - 'last_updated': , - 'state': '100000.35', - }) -# --- -# name: test_all_entities[sensor.rando_employer_investments_401_k_data_age-entry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': None, - 'config_entry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'sensor', - 'entity_category': , - 'entity_id': 'sensor.rando_employer_investments_401_k_data_age', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'labels': set({ - }), - 'name': None, - 'options': dict({ - }), - 'original_device_class': , - 'original_icon': None, - 'original_name': 'Data age', - 'platform': 'monarchmoney', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': 'age', - 'unique_id': '222260252323873333_401.k_age', - 'unit_of_measurement': None, - }) -# --- -# name: test_all_entities[sensor.rando_employer_investments_401_k_data_age-state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'attribution': 'Data provided by Monarch Money API via FINICITY', - 'device_class': 'timestamp', - 'friendly_name': 'Rando Employer Investments 401.k Data age', - }), - 'context': , - 'entity_id': 'sensor.rando_employer_investments_401_k_data_age', - 'last_changed': , - 'last_reported': , - 'last_updated': , - 'state': '2024-02-17T08:13:10+00:00', - }) -# --- -# name: test_all_entities[sensor.rando_employer_investments_401_k_first_sync-entry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': None, - 'config_entry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'sensor', - 'entity_category': , - 'entity_id': 'sensor.rando_employer_investments_401_k_first_sync', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'labels': set({ - }), - 'name': None, - 'options': dict({ - }), - 'original_device_class': , - 'original_icon': None, - 'original_name': 'First sync', - 'platform': 'monarchmoney', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': 'created', - 'unique_id': '222260252323873333_401.k_created', - 'unit_of_measurement': None, - }) -# --- -# name: test_all_entities[sensor.rando_employer_investments_401_k_first_sync-state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'attribution': 'Data provided by Monarch Money API via FINICITY', - 'device_class': 'timestamp', - 'friendly_name': 'Rando Employer Investments 401.k First sync', - }), - 'context': , - 'entity_id': 'sensor.rando_employer_investments_401_k_first_sync', - 'last_changed': , - 'last_reported': , - 'last_updated': , - 'state': '2021-10-15T01:41:54+00:00', - }) -# --- -# name: test_all_entities[sensor.rando_investments_roth_ira_balance-entry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': dict({ - 'state_class': , - }), - 'config_entry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'sensor', - 'entity_category': None, - 'entity_id': 'sensor.rando_investments_roth_ira_balance', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'labels': set({ - }), - 'name': None, - 'options': dict({ - }), - 'original_device_class': , - 'original_icon': None, - 'original_name': 'Balance', - 'platform': 'monarchmoney', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': 'balance', - 'unique_id': '222260252323873333_Roth IRA_balance', - 'unit_of_measurement': '$', - }) -# --- -# name: test_all_entities[sensor.rando_investments_roth_ira_balance-state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'attribution': 'Data provided by Monarch Money API via PLAID', - 'device_class': 'monetary', - 'entity_picture': 'data:image/png;base64,base64Nonce', - 'friendly_name': 'Rando Investments Roth IRA Balance', - 'state_class': , - 'unit_of_measurement': '$', - }), - 'context': , - 'entity_id': 'sensor.rando_investments_roth_ira_balance', - 'last_changed': , - 'last_reported': , - 'last_updated': , - 'state': '10000.43', - }) -# --- -# name: test_all_entities[sensor.rando_investments_roth_ira_data_age-entry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': None, - 'config_entry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'sensor', - 'entity_category': , - 'entity_id': 'sensor.rando_investments_roth_ira_data_age', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'labels': set({ - }), - 'name': None, - 'options': dict({ - }), - 'original_device_class': , - 'original_icon': None, - 'original_name': 'Data age', - 'platform': 'monarchmoney', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': 'age', - 'unique_id': '222260252323873333_Roth IRA_age', - 'unit_of_measurement': None, - }) -# --- -# name: test_all_entities[sensor.rando_investments_roth_ira_data_age-state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'attribution': 'Data provided by Monarch Money API via PLAID', - 'device_class': 'timestamp', - 'friendly_name': 'Rando Investments Roth IRA Data age', - }), - 'context': , - 'entity_id': 'sensor.rando_investments_roth_ira_data_age', - 'last_changed': , - 'last_reported': , - 'last_updated': , - 'state': '2024-02-17T13:32:21+00:00', - }) -# --- -# name: test_all_entities[sensor.rando_investments_roth_ira_first_sync-entry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': None, - 'config_entry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'sensor', - 'entity_category': , - 'entity_id': 'sensor.rando_investments_roth_ira_first_sync', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'labels': set({ - }), - 'name': None, - 'options': dict({ - }), - 'original_device_class': , - 'original_icon': None, - 'original_name': 'First sync', - 'platform': 'monarchmoney', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': 'created', - 'unique_id': '222260252323873333_Roth IRA_created', - 'unit_of_measurement': None, - }) -# --- -# name: test_all_entities[sensor.rando_investments_roth_ira_first_sync-state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'attribution': 'Data provided by Monarch Money API via PLAID', - 'device_class': 'timestamp', - 'friendly_name': 'Rando Investments Roth IRA First sync', - }), - 'context': , - 'entity_id': 'sensor.rando_investments_roth_ira_first_sync', - 'last_changed': , - 'last_reported': , - 'last_updated': , - 'state': '2021-10-15T01:35:59+00:00', - }) -# --- -# name: test_all_entities[sensor.rando_mortgage_mortgage_balance-entry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': dict({ - 'state_class': , - }), - 'config_entry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'sensor', - 'entity_category': None, - 'entity_id': 'sensor.rando_mortgage_mortgage_balance', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'labels': set({ - }), - 'name': None, - 'options': dict({ - }), - 'original_device_class': , - 'original_icon': None, - 'original_name': 'Balance', - 'platform': 'monarchmoney', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': 'balance', - 'unique_id': '222260252323873333_Mortgage_balance', - 'unit_of_measurement': '$', - }) -# --- -# name: test_all_entities[sensor.rando_mortgage_mortgage_balance-state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'attribution': 'Data provided by Monarch Money API via PLAID', - 'device_class': 'monetary', - 'entity_picture': 'data:image/png;base64,base64Nonce', - 'friendly_name': 'Rando Mortgage Mortgage Balance', - 'state_class': , - 'unit_of_measurement': '$', - }), - 'context': , - 'entity_id': 'sensor.rando_mortgage_mortgage_balance', - 'last_changed': , - 'last_reported': , - 'last_updated': , - 'state': '0.0', - }) -# --- -# name: test_all_entities[sensor.rando_mortgage_mortgage_data_age-entry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': None, - 'config_entry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'sensor', - 'entity_category': , - 'entity_id': 'sensor.rando_mortgage_mortgage_data_age', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'labels': set({ - }), - 'name': None, - 'options': dict({ - }), - 'original_device_class': , - 'original_icon': None, - 'original_name': 'Data age', - 'platform': 'monarchmoney', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': 'age', - 'unique_id': '222260252323873333_Mortgage_age', - 'unit_of_measurement': None, - }) -# --- -# name: test_all_entities[sensor.rando_mortgage_mortgage_data_age-state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'attribution': 'Data provided by Monarch Money API via PLAID', - 'device_class': 'timestamp', - 'friendly_name': 'Rando Mortgage Mortgage Data age', - }), - 'context': , - 'entity_id': 'sensor.rando_mortgage_mortgage_data_age', - 'last_changed': , - 'last_reported': , - 'last_updated': , - 'state': '2023-08-16T01:41:36+00:00', - }) -# --- -# name: test_all_entities[sensor.rando_mortgage_mortgage_first_sync-entry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': None, - 'config_entry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'sensor', - 'entity_category': , - 'entity_id': 'sensor.rando_mortgage_mortgage_first_sync', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'labels': set({ - }), - 'name': None, - 'options': dict({ - }), - 'original_device_class': , - 'original_icon': None, - 'original_name': 'First sync', - 'platform': 'monarchmoney', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': 'created', - 'unique_id': '222260252323873333_Mortgage_created', - 'unit_of_measurement': None, - }) -# --- -# name: test_all_entities[sensor.rando_mortgage_mortgage_first_sync-state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'attribution': 'Data provided by Monarch Money API via PLAID', - 'device_class': 'timestamp', - 'friendly_name': 'Rando Mortgage Mortgage First sync', - }), - 'context': , - 'entity_id': 'sensor.rando_mortgage_mortgage_first_sync', - 'last_changed': , - 'last_reported': , - 'last_updated': , - 'state': '2021-10-15T01:45:25+00:00', - }) -# --- -# name: test_all_entities[sensor.vinaudit_2050_toyota_rav8_balance-entry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': dict({ - 'state_class': , - }), - 'config_entry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'sensor', - 'entity_category': None, - 'entity_id': 'sensor.vinaudit_2050_toyota_rav8_balance', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'labels': set({ - }), - 'name': None, - 'options': dict({ - }), - 'original_device_class': , - 'original_icon': 'mdi:car', - 'original_name': 'Balance', - 'platform': 'monarchmoney', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': 'value', - 'unique_id': '222260252323873333_2050 Toyota RAV8_value', - 'unit_of_measurement': '$', - }) -# --- -# name: test_all_entities[sensor.vinaudit_2050_toyota_rav8_balance-state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'attribution': 'Data provided by Monarch Money API via vin_audit', - 'device_class': 'monetary', - 'entity_picture': 'https://api.monarchmoney.com/cdn-cgi/image/width=128/images/institution/159427559853802644', - 'friendly_name': 'VinAudit 2050 Toyota RAV8 Balance', - 'icon': 'mdi:car', - 'state_class': , - 'unit_of_measurement': '$', - }), - 'context': , - 'entity_id': 'sensor.vinaudit_2050_toyota_rav8_balance', - 'last_changed': , - 'last_reported': , - 'last_updated': , - 'state': '11075.58', - }) -# --- -# name: test_all_entities[sensor.vinaudit_2050_toyota_rav8_data_age-entry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': None, - 'config_entry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'sensor', - 'entity_category': , - 'entity_id': 'sensor.vinaudit_2050_toyota_rav8_data_age', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'labels': set({ - }), - 'name': None, - 'options': dict({ - }), - 'original_device_class': , - 'original_icon': None, - 'original_name': 'Data age', - 'platform': 'monarchmoney', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': 'age', - 'unique_id': '222260252323873333_2050 Toyota RAV8_age', - 'unit_of_measurement': None, - }) -# --- -# name: test_all_entities[sensor.vinaudit_2050_toyota_rav8_data_age-state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'attribution': 'Data provided by Monarch Money API via Manual entry', - 'device_class': 'timestamp', - 'friendly_name': 'VinAudit 2050 Toyota RAV8 Data age', - }), - 'context': , - 'entity_id': 'sensor.vinaudit_2050_toyota_rav8_data_age', - 'last_changed': , - 'last_reported': , - 'last_updated': , - 'state': '2024-08-16T17:37:21+00:00', - }) -# --- -# name: test_all_entities[sensor.vinaudit_2050_toyota_rav8_first_sync-entry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': None, - 'config_entry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'sensor', - 'entity_category': , - 'entity_id': 'sensor.vinaudit_2050_toyota_rav8_first_sync', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'labels': set({ - }), - 'name': None, - 'options': dict({ - }), - 'original_device_class': , - 'original_icon': None, - 'original_name': 'First sync', - 'platform': 'monarchmoney', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': 'created', - 'unique_id': '222260252323873333_2050 Toyota RAV8_created', - 'unit_of_measurement': None, - }) -# --- -# name: test_all_entities[sensor.vinaudit_2050_toyota_rav8_first_sync-state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'attribution': 'Data provided by Monarch Money API via Manual entry', - 'device_class': 'timestamp', - 'friendly_name': 'VinAudit 2050 Toyota RAV8 First sync', - }), - 'context': , - 'entity_id': 'sensor.vinaudit_2050_toyota_rav8_first_sync', - 'last_changed': , - 'last_reported': , - 'last_updated': , - 'state': '2024-08-16T17:37:21+00:00', - }) -# --- -# name: test_all_entities[sensor.vinaudit_2050_toyota_rav8_value-entry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': dict({ - 'state_class': , - }), - 'config_entry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'sensor', - 'entity_category': None, - 'entity_id': 'sensor.vinaudit_2050_toyota_rav8_value', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'labels': set({ - }), - 'name': None, - 'options': dict({ - }), - 'original_device_class': , - 'original_icon': None, - 'original_name': 'Value', - 'platform': 'monarchmoney', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': 'value', - 'unique_id': '222260252323873333_2050 Toyota RAV8_value', - 'unit_of_measurement': '$', - }) -# --- -# name: test_all_entities[sensor.vinaudit_2050_toyota_rav8_value-state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'attribution': 'Data provided by Monarch Money API via Manual entry', - 'device_class': 'monetary', - 'entity_picture': 'https://api.monarchmoney.com/cdn-cgi/image/width=128/images/institution/159427559853802644', - 'friendly_name': 'VinAudit 2050 Toyota RAV8 Value', - 'state_class': , - 'unit_of_measurement': '$', - }), - 'context': , - 'entity_id': 'sensor.vinaudit_2050_toyota_rav8_value', - 'last_changed': , - 'last_reported': , - 'last_updated': , - 'state': '11075.58', - }) -# --- -# name: test_all_entities[sensor.zillow_house_balance-entry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': dict({ - 'state_class': , - }), - 'config_entry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'sensor', - 'entity_category': None, - 'entity_id': 'sensor.zillow_house_balance', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'labels': set({ - }), - 'name': None, - 'options': dict({ - }), - 'original_device_class': , - 'original_icon': None, - 'original_name': 'Balance', - 'platform': 'monarchmoney', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': 'balance', - 'unique_id': '222260252323873333_House_balance', - 'unit_of_measurement': '$', - }) -# --- -# name: test_all_entities[sensor.zillow_house_balance-state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'attribution': 'Data provided by Monarch Money API via Manual entry', - 'device_class': 'monetary', - 'entity_picture': 'data:image/png;base64,base64Nonce', - 'friendly_name': 'Zillow House Balance', - 'state_class': , - 'unit_of_measurement': '$', - }), - 'context': , - 'entity_id': 'sensor.zillow_house_balance', - 'last_changed': , - 'last_reported': , - 'last_updated': , - 'state': '123000.0', - }) -# --- -# name: test_all_entities[sensor.zillow_house_data_age-entry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': None, - 'config_entry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'sensor', - 'entity_category': , - 'entity_id': 'sensor.zillow_house_data_age', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'labels': set({ - }), - 'name': None, - 'options': dict({ - }), - 'original_device_class': , - 'original_icon': None, - 'original_name': 'Data age', - 'platform': 'monarchmoney', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': 'age', - 'unique_id': '222260252323873333_House_age', - 'unit_of_measurement': None, - }) -# --- -# name: test_all_entities[sensor.zillow_house_data_age-state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'attribution': 'Data provided by Monarch Money API via Manual entry', - 'device_class': 'timestamp', - 'friendly_name': 'Zillow House Data age', - }), - 'context': , - 'entity_id': 'sensor.zillow_house_data_age', - 'last_changed': , - 'last_reported': , - 'last_updated': , - 'state': '2024-02-12T09:00:25+00:00', - }) -# --- -# name: test_all_entities[sensor.zillow_house_first_sync-entry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': None, - 'config_entry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'sensor', - 'entity_category': , - 'entity_id': 'sensor.zillow_house_first_sync', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'labels': set({ - }), - 'name': None, - 'options': dict({ - }), - 'original_device_class': , - 'original_icon': None, - 'original_name': 'First sync', - 'platform': 'monarchmoney', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': 'created', - 'unique_id': '222260252323873333_House_created', - 'unit_of_measurement': None, - }) -# --- -# name: test_all_entities[sensor.zillow_house_first_sync-state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'attribution': 'Data provided by Monarch Money API via Manual entry', - 'device_class': 'timestamp', - 'friendly_name': 'Zillow House First sync', - }), - 'context': , - 'entity_id': 'sensor.zillow_house_first_sync', - 'last_changed': , - 'last_reported': , - 'last_updated': , - 'state': '2021-10-15T01:39:29+00:00', - }) -# --- From 68b4038d01742f3317ad4902fd6c37d19aad854c Mon Sep 17 00:00:00 2001 From: Jeff Stein Date: Thu, 5 Sep 2024 16:22:01 -0600 Subject: [PATCH 38/67] backing lib update --- homeassistant/components/monarchmoney/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/monarchmoney/manifest.json b/homeassistant/components/monarchmoney/manifest.json index 16afcaafc7ea90..dfd57838c362bb 100644 --- a/homeassistant/components/monarchmoney/manifest.json +++ b/homeassistant/components/monarchmoney/manifest.json @@ -5,5 +5,5 @@ "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/monarchmoney", "iot_class": "cloud_polling", - "requirements": ["monarchmoney-typed==0.1.3"] + "requirements": ["monarchmoney-typed==0.1.5"] } diff --git a/requirements_all.txt b/requirements_all.txt index f07309b9a903dd..07e428edc138fa 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1369,7 +1369,7 @@ moat-ble==0.1.1 moehlenhoff-alpha2==1.3.1 # homeassistant.components.monarchmoney -monarchmoney-typed==0.1.3 +monarchmoney-typed==0.1.5 # homeassistant.components.monzo monzopy==1.3.2 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index acc5dc55ed728d..7cc85e058288eb 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1138,7 +1138,7 @@ moat-ble==0.1.1 moehlenhoff-alpha2==1.3.1 # homeassistant.components.monarchmoney -monarchmoney-typed==0.1.3 +monarchmoney-typed==0.1.5 # homeassistant.components.monzo monzopy==1.3.2 From 6b3675f5e92ca2b5da88c85245126a66437ad772 Mon Sep 17 00:00:00 2001 From: Jeff Stein Date: Thu, 5 Sep 2024 16:30:46 -0600 Subject: [PATCH 39/67] fixing manifest --- homeassistant/components/monarchmoney/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/monarchmoney/manifest.json b/homeassistant/components/monarchmoney/manifest.json index dfd57838c362bb..f7eb9b6e0c1c11 100644 --- a/homeassistant/components/monarchmoney/manifest.json +++ b/homeassistant/components/monarchmoney/manifest.json @@ -5,5 +5,5 @@ "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/monarchmoney", "iot_class": "cloud_polling", - "requirements": ["monarchmoney-typed==0.1.5"] + "requirements": ["monarchmoney_typed==0.1.5"] } diff --git a/requirements_all.txt b/requirements_all.txt index 07e428edc138fa..78d05e8850382b 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1369,7 +1369,7 @@ moat-ble==0.1.1 moehlenhoff-alpha2==1.3.1 # homeassistant.components.monarchmoney -monarchmoney-typed==0.1.5 +monarchmoney_typed==0.1.5 # homeassistant.components.monzo monzopy==1.3.2 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 7cc85e058288eb..22e9024489e19f 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1138,7 +1138,7 @@ moat-ble==0.1.1 moehlenhoff-alpha2==1.3.1 # homeassistant.components.monarchmoney -monarchmoney-typed==0.1.5 +monarchmoney_typed==0.1.5 # homeassistant.components.monzo monzopy==1.3.2 From 2aa2669a2d9459c419982228c384f2ff7e2dddfd Mon Sep 17 00:00:00 2001 From: Jeff Stein Date: Thu, 5 Sep 2024 16:40:08 -0600 Subject: [PATCH 40/67] fixing manifest --- homeassistant/components/monarchmoney/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/monarchmoney/manifest.json b/homeassistant/components/monarchmoney/manifest.json index f7eb9b6e0c1c11..5b9c35b8b644a6 100644 --- a/homeassistant/components/monarchmoney/manifest.json +++ b/homeassistant/components/monarchmoney/manifest.json @@ -5,5 +5,5 @@ "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/monarchmoney", "iot_class": "cloud_polling", - "requirements": ["monarchmoney_typed==0.1.5"] + "requirements": ["monarchmoney_typed==0.1.6"] } diff --git a/requirements_all.txt b/requirements_all.txt index 78d05e8850382b..a6875fe8ee42a9 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1369,7 +1369,7 @@ moat-ble==0.1.1 moehlenhoff-alpha2==1.3.1 # homeassistant.components.monarchmoney -monarchmoney_typed==0.1.5 +monarchmoney_typed==0.1.6 # homeassistant.components.monzo monzopy==1.3.2 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 22e9024489e19f..f5202eacfe835d 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1138,7 +1138,7 @@ moat-ble==0.1.1 moehlenhoff-alpha2==1.3.1 # homeassistant.components.monarchmoney -monarchmoney_typed==0.1.5 +monarchmoney_typed==0.1.6 # homeassistant.components.monzo monzopy==1.3.2 From 364ba09262eea18d3a13e621cae098094e3adec6 Mon Sep 17 00:00:00 2001 From: Jeff Stein Date: Thu, 5 Sep 2024 16:46:17 -0600 Subject: [PATCH 41/67] fixing manifest --- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements_all.txt b/requirements_all.txt index a6875fe8ee42a9..05ab9ec6099df8 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1369,7 +1369,7 @@ moat-ble==0.1.1 moehlenhoff-alpha2==1.3.1 # homeassistant.components.monarchmoney -monarchmoney_typed==0.1.6 +monarchmoney-typed==0.1.6 # homeassistant.components.monzo monzopy==1.3.2 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index f5202eacfe835d..8dfe336e4ab2ac 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1138,7 +1138,7 @@ moat-ble==0.1.1 moehlenhoff-alpha2==1.3.1 # homeassistant.components.monarchmoney -monarchmoney_typed==0.1.6 +monarchmoney-typed==0.1.6 # homeassistant.components.monzo monzopy==1.3.2 From d7751eb3c9d08bdd31396fd3f99cae40aeae4c1a Mon Sep 17 00:00:00 2001 From: Jeff Stein Date: Thu, 5 Sep 2024 16:56:17 -0600 Subject: [PATCH 42/67] Version 0.2.0 --- homeassistant/components/monarchmoney/manifest.json | 2 +- requirements_all.txt | 6 +++--- requirements_test_all.txt | 6 +++--- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/homeassistant/components/monarchmoney/manifest.json b/homeassistant/components/monarchmoney/manifest.json index 5b9c35b8b644a6..e0b8cdc8edd790 100644 --- a/homeassistant/components/monarchmoney/manifest.json +++ b/homeassistant/components/monarchmoney/manifest.json @@ -5,5 +5,5 @@ "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/monarchmoney", "iot_class": "cloud_polling", - "requirements": ["monarchmoney_typed==0.1.6"] + "requirements": ["typedmonarchmoney==0.2.0"] } diff --git a/requirements_all.txt b/requirements_all.txt index 05ab9ec6099df8..ac8d64dbbe66f3 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1368,9 +1368,6 @@ moat-ble==0.1.1 # homeassistant.components.moehlenhoff_alpha2 moehlenhoff-alpha2==1.3.1 -# homeassistant.components.monarchmoney -monarchmoney-typed==0.1.6 - # homeassistant.components.monzo monzopy==1.3.2 @@ -2857,6 +2854,9 @@ twilio==6.32.0 # homeassistant.components.twitch twitchAPI==4.2.1 +# homeassistant.components.monarchmoney +typedmonarchmoney==0.2.0 + # homeassistant.components.ukraine_alarm uasiren==0.0.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 8dfe336e4ab2ac..8b701e0d5e855f 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1137,9 +1137,6 @@ moat-ble==0.1.1 # homeassistant.components.moehlenhoff_alpha2 moehlenhoff-alpha2==1.3.1 -# homeassistant.components.monarchmoney -monarchmoney-typed==0.1.6 - # homeassistant.components.monzo monzopy==1.3.2 @@ -2258,6 +2255,9 @@ twilio==6.32.0 # homeassistant.components.twitch twitchAPI==4.2.1 +# homeassistant.components.monarchmoney +typedmonarchmoney==0.2.0 + # homeassistant.components.ukraine_alarm uasiren==0.0.1 From 2ec65ad4a458aa34d30813b33ef57c054923b002 Mon Sep 17 00:00:00 2001 From: Jeff Stein Date: Thu, 5 Sep 2024 18:09:11 -0600 Subject: [PATCH 43/67] fixing some types --- homeassistant/components/monarchmoney/__init__.py | 2 +- homeassistant/components/monarchmoney/coordinator.py | 4 ++-- tests/components/monarchmoney/conftest.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/monarchmoney/__init__.py b/homeassistant/components/monarchmoney/__init__.py index f80b2412e56043..5f9aba7dd07297 100644 --- a/homeassistant/components/monarchmoney/__init__.py +++ b/homeassistant/components/monarchmoney/__init__.py @@ -2,7 +2,7 @@ from __future__ import annotations -from monarchmoney_typed import TypedMonarchMoney +from typedmonarchmoney import TypedMonarchMoney from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_TOKEN, Platform diff --git a/homeassistant/components/monarchmoney/coordinator.py b/homeassistant/components/monarchmoney/coordinator.py index 2c4aac7c6109da..f4167958594dea 100644 --- a/homeassistant/components/monarchmoney/coordinator.py +++ b/homeassistant/components/monarchmoney/coordinator.py @@ -7,8 +7,8 @@ from aiohttp import ClientResponseError from gql.transport.exceptions import TransportServerError from monarchmoney import LoginFailedException -from monarchmoney_typed import TypedMonarchMoney -from monarchmoney_typed.models import MonarchAccount, MonarchCashflowSummary +from typedmonarchmoney import TypedMonarchMoney +from typedmonarchmoney.models import MonarchAccount, MonarchCashflowSummary from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant diff --git a/tests/components/monarchmoney/conftest.py b/tests/components/monarchmoney/conftest.py index feb998ae487874..1ce09d24fa9ba2 100644 --- a/tests/components/monarchmoney/conftest.py +++ b/tests/components/monarchmoney/conftest.py @@ -5,8 +5,8 @@ from typing import Any from unittest.mock import AsyncMock, PropertyMock, patch -from monarchmoney_typed.models import MonarchAccount, MonarchCashflowSummary import pytest +from typedmonarchmoney.models import MonarchAccount, MonarchCashflowSummary from homeassistant.components.monarchmoney.const import DOMAIN from homeassistant.const import CONF_TOKEN From 0cfd952fe53981226841d1176a1cff90b5c5bea3 Mon Sep 17 00:00:00 2001 From: Jeff Stein Date: Thu, 5 Sep 2024 18:13:04 -0600 Subject: [PATCH 44/67] more type fixes --- homeassistant/components/monarchmoney/config_flow.py | 2 +- homeassistant/components/monarchmoney/entity.py | 2 +- homeassistant/components/monarchmoney/sensor.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/monarchmoney/config_flow.py b/homeassistant/components/monarchmoney/config_flow.py index bc068e736c000e..84229866ceab21 100644 --- a/homeassistant/components/monarchmoney/config_flow.py +++ b/homeassistant/components/monarchmoney/config_flow.py @@ -7,7 +7,7 @@ from monarchmoney import LoginFailedException, RequireMFAException from monarchmoney.monarchmoney import SESSION_FILE -from monarchmoney_typed import TypedMonarchMoney +from typedmonarchmoney import TypedMonarchMoney import voluptuous as vol from homeassistant.config_entries import ConfigFlow, ConfigFlowResult diff --git a/homeassistant/components/monarchmoney/entity.py b/homeassistant/components/monarchmoney/entity.py index a3a0eb9edfaf83..16dfd47b3f6f80 100644 --- a/homeassistant/components/monarchmoney/entity.py +++ b/homeassistant/components/monarchmoney/entity.py @@ -1,6 +1,6 @@ """Monarch money entity definition.""" -from monarchmoney_typed.models import MonarchAccount, MonarchCashflowSummary +from typedmonarchmoney.models import MonarchAccount, MonarchCashflowSummary from homeassistant.helpers.device_registry import DeviceEntryType, DeviceInfo from homeassistant.helpers.entity import EntityDescription diff --git a/homeassistant/components/monarchmoney/sensor.py b/homeassistant/components/monarchmoney/sensor.py index d27d0f3e697dfd..58562c0704dead 100644 --- a/homeassistant/components/monarchmoney/sensor.py +++ b/homeassistant/components/monarchmoney/sensor.py @@ -5,7 +5,7 @@ from datetime import datetime from typing import Any -from monarchmoney_typed.models import MonarchAccount, MonarchCashflowSummary +from typedmonarchmoney.models import MonarchAccount, MonarchCashflowSummary from homeassistant.components.sensor import ( SensorDeviceClass, From 222a0b61116427437c8857f234a116dd1b6ebeed Mon Sep 17 00:00:00 2001 From: Jeff Stein Date: Thu, 5 Sep 2024 18:18:30 -0600 Subject: [PATCH 45/67] cleanup and bump --- .../components/monarchmoney/entity.py | 20 +++---------------- .../components/monarchmoney/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 4 files changed, 6 insertions(+), 20 deletions(-) diff --git a/homeassistant/components/monarchmoney/entity.py b/homeassistant/components/monarchmoney/entity.py index 16dfd47b3f6f80..0a1f2b57c3133c 100644 --- a/homeassistant/components/monarchmoney/entity.py +++ b/homeassistant/components/monarchmoney/entity.py @@ -56,31 +56,17 @@ def __init__( self.entity_description = description self._account_id = account.id - - # Parse out some fields - institution = "Manual entry" - if account.institution_name is not None: - institution = account.institution_name - configuration_url = "http://monarchmoney.com" - if account.institution_url is not None: - configuration_url = account.institution_url - self._attr_attribution = ( f"Data provided by Monarch Money API via {account.data_provider}" ) - - if not configuration_url.startswith(("http://", "https://")): - configuration_url = f"http://{configuration_url}" - self._attr_unique_id = f"{coordinator.subscription_id}_{account.name}_{description.translation_key}" - self._attr_device_info = DeviceInfo( identifiers={(DOMAIN, account.id)}, - name=f"{institution} {account.name}", + name=f"{account.institution_name} {account.name}", entry_type=DeviceEntryType.SERVICE, manufacturer=account.data_provider, - model=f"{institution} - {account.type_name} - {account.subtype_name}", - configuration_url=configuration_url, + model=f"{account.institution_name} - {account.type_name} - {account.subtype_name}", + configuration_url=account.institution_url, suggested_area="Banking/Finance", ) diff --git a/homeassistant/components/monarchmoney/manifest.json b/homeassistant/components/monarchmoney/manifest.json index e0b8cdc8edd790..422adf795daa06 100644 --- a/homeassistant/components/monarchmoney/manifest.json +++ b/homeassistant/components/monarchmoney/manifest.json @@ -5,5 +5,5 @@ "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/monarchmoney", "iot_class": "cloud_polling", - "requirements": ["typedmonarchmoney==0.2.0"] + "requirements": ["typedmonarchmoney==0.2.1"] } diff --git a/requirements_all.txt b/requirements_all.txt index ac8d64dbbe66f3..2ddb6e3ea42378 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2855,7 +2855,7 @@ twilio==6.32.0 twitchAPI==4.2.1 # homeassistant.components.monarchmoney -typedmonarchmoney==0.2.0 +typedmonarchmoney==0.2.1 # homeassistant.components.ukraine_alarm uasiren==0.0.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 8b701e0d5e855f..c3a50309bb7cf7 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -2256,7 +2256,7 @@ twilio==6.32.0 twitchAPI==4.2.1 # homeassistant.components.monarchmoney -typedmonarchmoney==0.2.0 +typedmonarchmoney==0.2.1 # homeassistant.components.ukraine_alarm uasiren==0.0.1 From 6c8e90528464296dd3b97b1e05cbd887ca9114a7 Mon Sep 17 00:00:00 2001 From: Jeff Stein Date: Fri, 6 Sep 2024 06:09:03 -0600 Subject: [PATCH 46/67] no check --- homeassistant/components/monarchmoney/entity.py | 4 +++- homeassistant/components/monarchmoney/sensor.py | 9 +-------- homeassistant/components/monarchmoney/strings.json | 6 +++--- 3 files changed, 7 insertions(+), 12 deletions(-) diff --git a/homeassistant/components/monarchmoney/entity.py b/homeassistant/components/monarchmoney/entity.py index 0a1f2b57c3133c..4e087afa9c784a 100644 --- a/homeassistant/components/monarchmoney/entity.py +++ b/homeassistant/components/monarchmoney/entity.py @@ -59,7 +59,9 @@ def __init__( self._attr_attribution = ( f"Data provided by Monarch Money API via {account.data_provider}" ) - self._attr_unique_id = f"{coordinator.subscription_id}_{account.name}_{description.translation_key}" + self._attr_unique_id = ( + f"{coordinator.subscription_id}_{account.id}_{description.translation_key}" + ) self._attr_device_info = DeviceInfo( identifiers={(DOMAIN, account.id)}, name=f"{account.institution_name} {account.name}", diff --git a/homeassistant/components/monarchmoney/sensor.py b/homeassistant/components/monarchmoney/sensor.py index 58562c0704dead..07c8309e23d75d 100644 --- a/homeassistant/components/monarchmoney/sensor.py +++ b/homeassistant/components/monarchmoney/sensor.py @@ -26,7 +26,7 @@ class MonarchMoneySensorEntityDescription(SensorEntityDescription): """Describe a sensor entity.""" - value_fn: Callable[[MonarchAccount], StateType | datetime] | None = None + value_fn: Callable[[MonarchAccount], StateType | datetime] summary_fn: Callable[[MonarchCashflowSummary], StateType] | None = None picture_fn: Callable[[Any], str] | None = None @@ -65,13 +65,6 @@ class MonarchMoneySensorEntityDescription(SensorEntityDescription): entity_category=EntityCategory.DIAGNOSTIC, value_fn=lambda account: account.last_update, ), - MonarchMoneySensorEntityDescription( - key="created", - translation_key="created", - device_class=SensorDeviceClass.TIMESTAMP, - entity_category=EntityCategory.DIAGNOSTIC, - value_fn=lambda account: account.date_created, - ), ) MONARCH_CASHFLOW_SENSORS: tuple[MonarchMoneySensorEntityDescription, ...] = ( diff --git a/homeassistant/components/monarchmoney/strings.json b/homeassistant/components/monarchmoney/strings.json index 2e7b049a33766d..5622a0454c0f4a 100644 --- a/homeassistant/components/monarchmoney/strings.json +++ b/homeassistant/components/monarchmoney/strings.json @@ -30,13 +30,13 @@ }, "sum_income": { - "name": "Income YTD" + "name": "Income Year to Date" }, "sum_expense": { - "name": "Expense YTD" + "name": "Expense Year to Date" }, "savings": { - "name": "Savings YTD" + "name": "Savings Year to Date" }, "savings_rate": { "name": "Savings rate" From 944798c8f55e8f5f0f31a12bd479e17fe8276d9b Mon Sep 17 00:00:00 2001 From: Jeff Stein Date: Fri, 6 Sep 2024 13:54:17 -0600 Subject: [PATCH 47/67] i think i got it all --- .../components/monarchmoney/config_flow.py | 6 ++- .../components/monarchmoney/coordinator.py | 13 ++++-- .../components/monarchmoney/entity.py | 15 ++++--- .../components/monarchmoney/manifest.json | 2 +- .../components/monarchmoney/sensor.py | 44 ++++++++++++------- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/monarchmoney/conftest.py | 26 +++++------ 8 files changed, 63 insertions(+), 47 deletions(-) diff --git a/homeassistant/components/monarchmoney/config_flow.py b/homeassistant/components/monarchmoney/config_flow.py index 84229866ceab21..bd7cdfe64f8dee 100644 --- a/homeassistant/components/monarchmoney/config_flow.py +++ b/homeassistant/components/monarchmoney/config_flow.py @@ -8,6 +8,7 @@ from monarchmoney import LoginFailedException, RequireMFAException from monarchmoney.monarchmoney import SESSION_FILE from typedmonarchmoney import TypedMonarchMoney +from typedmonarchmoney.models import MonarchSubscription import voluptuous as vol from homeassistant.config_entries import ConfigFlow, ConfigFlowResult @@ -88,8 +89,9 @@ async def validate_login( LOGGER.debug(f"Connection successful - saving session to file {SESSION_FILE}") LOGGER.debug("Obtaining subscription id") - subs = await monarch_client.get_subscription_details() - subscription_id = subs["subscription"]["id"] + subs: MonarchSubscription = await monarch_client.get_subscription_details() + assert subs is not None + subscription_id = subs.id return { "title": "Monarch Money", CONF_TOKEN: monarch_client.token, diff --git a/homeassistant/components/monarchmoney/coordinator.py b/homeassistant/components/monarchmoney/coordinator.py index f4167958594dea..a5a2723017dcac 100644 --- a/homeassistant/components/monarchmoney/coordinator.py +++ b/homeassistant/components/monarchmoney/coordinator.py @@ -8,7 +8,11 @@ from gql.transport.exceptions import TransportServerError from monarchmoney import LoginFailedException from typedmonarchmoney import TypedMonarchMoney -from typedmonarchmoney.models import MonarchAccount, MonarchCashflowSummary +from typedmonarchmoney.models import ( + MonarchAccount, + MonarchCashflowSummary, + MonarchSubscription, +) from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant @@ -49,11 +53,12 @@ def __init__( async def _async_setup(self) -> None: """Obtain subscription ID in setup phase.""" try: - sub_details = await self.client.get_subscription_details() + sub_details: MonarchSubscription = ( + await self.client.get_subscription_details() + ) except (TransportServerError, LoginFailedException, ClientResponseError) as err: raise ConfigEntryError("Authentication failed") from err - - self.subscription_id = sub_details["subscription"]["id"] + self.subscription_id = sub_details.id async def _async_update_data(self) -> MonarchData: """Fetch data for all accounts.""" diff --git a/homeassistant/components/monarchmoney/entity.py b/homeassistant/components/monarchmoney/entity.py index 4e087afa9c784a..969995ceaeaeb9 100644 --- a/homeassistant/components/monarchmoney/entity.py +++ b/homeassistant/components/monarchmoney/entity.py @@ -17,7 +17,7 @@ class MonarchMoneyEntityBase(CoordinatorEntity[MonarchMoneyDataUpdateCoordinator class MonarchMoneyCashFlowEntity(MonarchMoneyEntityBase): - """Custom entity for Cashflow sensors.""" + """Entity for Cashflow sensors.""" def __init__( self, @@ -31,9 +31,8 @@ def __init__( ) self.entity_description = description self._attr_device_info = DeviceInfo( - identifiers={(DOMAIN, "monarch_money_cashflow")}, + identifiers={(DOMAIN, coordinator.subscription_id)}, name="Cashflow", - suggested_area="Banking/Finance", ) @property @@ -43,7 +42,7 @@ def summary_data(self) -> MonarchCashflowSummary: class MonarchMoneyAccountEntity(MonarchMoneyEntityBase): - """Define a generic class for Entities.""" + """Entity for Account Sensors.""" def __init__( self, @@ -69,7 +68,13 @@ def __init__( manufacturer=account.data_provider, model=f"{account.institution_name} - {account.type_name} - {account.subtype_name}", configuration_url=account.institution_url, - suggested_area="Banking/Finance", + ) + + @property + def available(self) -> bool: + """Return if entity is available.""" + return super().available and ( + self._account_id in [x.id for x in self.coordinator.data.account_data] ) @property diff --git a/homeassistant/components/monarchmoney/manifest.json b/homeassistant/components/monarchmoney/manifest.json index 422adf795daa06..080b46d2cba7a8 100644 --- a/homeassistant/components/monarchmoney/manifest.json +++ b/homeassistant/components/monarchmoney/manifest.json @@ -5,5 +5,5 @@ "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/monarchmoney", "iot_class": "cloud_polling", - "requirements": ["typedmonarchmoney==0.2.1"] + "requirements": ["typedmonarchmoney==0.2.3"] } diff --git a/homeassistant/components/monarchmoney/sensor.py b/homeassistant/components/monarchmoney/sensor.py index 07c8309e23d75d..47d2fd05017ddb 100644 --- a/homeassistant/components/monarchmoney/sensor.py +++ b/homeassistant/components/monarchmoney/sensor.py @@ -3,7 +3,6 @@ from collections.abc import Callable from dataclasses import dataclass from datetime import datetime -from typing import Any from typedmonarchmoney.models import MonarchAccount, MonarchCashflowSummary @@ -24,16 +23,27 @@ @dataclass(frozen=True, kw_only=True) class MonarchMoneySensorEntityDescription(SensorEntityDescription): - """Describe a sensor entity.""" + """Base common class.""" + + +@dataclass(frozen=True, kw_only=True) +class MonarchMoneyAccountSensorEntityDescription(MonarchMoneySensorEntityDescription): + """Describe an account sensor entity.""" value_fn: Callable[[MonarchAccount], StateType | datetime] - summary_fn: Callable[[MonarchCashflowSummary], StateType] | None = None - picture_fn: Callable[[Any], str] | None = None + picture_fn: Callable[[MonarchAccount], str | None] | None = None + + +@dataclass(frozen=True, kw_only=True) +class MonarchMoneyCashflowSensorEntityDescription(MonarchMoneySensorEntityDescription): + """Describe a cashflow sensor entity.""" + + summary_fn: Callable[[MonarchCashflowSummary], StateType] | None # These sensors include assets like a boat that might have value -MONARCH_MONEY_VALUE_SENSORS: tuple[MonarchMoneySensorEntityDescription, ...] = ( - MonarchMoneySensorEntityDescription( +MONARCH_MONEY_VALUE_SENSORS: tuple[MonarchMoneyAccountSensorEntityDescription, ...] = ( + MonarchMoneyAccountSensorEntityDescription( key="value", translation_key="value", state_class=SensorStateClass.TOTAL, @@ -45,8 +55,8 @@ class MonarchMoneySensorEntityDescription(SensorEntityDescription): ) # Most accounts are balance sensors -MONARCH_MONEY_SENSORS: tuple[MonarchMoneySensorEntityDescription, ...] = ( - MonarchMoneySensorEntityDescription( +MONARCH_MONEY_SENSORS: tuple[MonarchMoneyAccountSensorEntityDescription, ...] = ( + MonarchMoneyAccountSensorEntityDescription( key="currentBalance", translation_key="balance", state_class=SensorStateClass.TOTAL, @@ -57,8 +67,8 @@ class MonarchMoneySensorEntityDescription(SensorEntityDescription): ), ) -MONARCH_MONEY_AGE_SENSORS: tuple[MonarchMoneySensorEntityDescription, ...] = ( - MonarchMoneySensorEntityDescription( +MONARCH_MONEY_AGE_SENSORS: tuple[MonarchMoneyAccountSensorEntityDescription, ...] = ( + MonarchMoneyAccountSensorEntityDescription( key="age", translation_key="age", device_class=SensorDeviceClass.TIMESTAMP, @@ -67,8 +77,8 @@ class MonarchMoneySensorEntityDescription(SensorEntityDescription): ), ) -MONARCH_CASHFLOW_SENSORS: tuple[MonarchMoneySensorEntityDescription, ...] = ( - MonarchMoneySensorEntityDescription( +MONARCH_CASHFLOW_SENSORS: tuple[MonarchMoneyCashflowSensorEntityDescription, ...] = ( + MonarchMoneyCashflowSensorEntityDescription( key="sum_income", translation_key="sum_income", summary_fn=lambda summary: summary.income, @@ -77,7 +87,7 @@ class MonarchMoneySensorEntityDescription(SensorEntityDescription): native_unit_of_measurement=CURRENCY_DOLLAR, icon="mdi:cash-plus", ), - MonarchMoneySensorEntityDescription( + MonarchMoneyCashflowSensorEntityDescription( key="sum_expense", translation_key="sum_expense", summary_fn=lambda summary: summary.expenses, @@ -86,7 +96,7 @@ class MonarchMoneySensorEntityDescription(SensorEntityDescription): native_unit_of_measurement=CURRENCY_DOLLAR, icon="mdi:cash-minus", ), - MonarchMoneySensorEntityDescription( + MonarchMoneyCashflowSensorEntityDescription( key="savings", translation_key="savings", summary_fn=lambda summary: summary.savings, @@ -95,7 +105,7 @@ class MonarchMoneySensorEntityDescription(SensorEntityDescription): native_unit_of_measurement=CURRENCY_DOLLAR, icon="mdi:piggy-bank-outline", ), - MonarchMoneySensorEntityDescription( + MonarchMoneyCashflowSensorEntityDescription( key="savings_rate", translation_key="savings_rate", summary_fn=lambda summary: summary.savings_rate * 100, @@ -161,7 +171,7 @@ async def async_setup_entry( class MonarchMoneyCashFlowSensor(MonarchMoneyCashFlowEntity, SensorEntity): """Cashflow summary sensor.""" - entity_description: MonarchMoneySensorEntityDescription + entity_description: MonarchMoneyCashflowSensorEntityDescription @property def native_value(self) -> StateType | datetime: @@ -174,7 +184,7 @@ def native_value(self) -> StateType | datetime: class MonarchMoneySensor(MonarchMoneyAccountEntity, SensorEntity): """Define a monarch money sensor.""" - entity_description: MonarchMoneySensorEntityDescription + entity_description: MonarchMoneyAccountSensorEntityDescription @property def native_value(self) -> StateType | datetime | None: diff --git a/requirements_all.txt b/requirements_all.txt index 2ddb6e3ea42378..8a26b2e4f9e591 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2855,7 +2855,7 @@ twilio==6.32.0 twitchAPI==4.2.1 # homeassistant.components.monarchmoney -typedmonarchmoney==0.2.1 +typedmonarchmoney==0.2.3 # homeassistant.components.ukraine_alarm uasiren==0.0.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index c3a50309bb7cf7..61081499b023f9 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -2256,7 +2256,7 @@ twilio==6.32.0 twitchAPI==4.2.1 # homeassistant.components.monarchmoney -typedmonarchmoney==0.2.1 +typedmonarchmoney==0.2.3 # homeassistant.components.ukraine_alarm uasiren==0.0.1 diff --git a/tests/components/monarchmoney/conftest.py b/tests/components/monarchmoney/conftest.py index 1ce09d24fa9ba2..db51bfc66e05fe 100644 --- a/tests/components/monarchmoney/conftest.py +++ b/tests/components/monarchmoney/conftest.py @@ -6,7 +6,11 @@ from unittest.mock import AsyncMock, PropertyMock, patch import pytest -from typedmonarchmoney.models import MonarchAccount, MonarchCashflowSummary +from typedmonarchmoney.models import ( + MonarchAccount, + MonarchCashflowSummary, + MonarchSubscription, +) from homeassistant.components.monarchmoney.const import DOMAIN from homeassistant.const import CONF_TOKEN @@ -44,8 +48,9 @@ def mock_config_api() -> Generator[AsyncMock]: load_fixture("get_cashflow_summary.json", DOMAIN) ) cashflow_summary = MonarchCashflowSummary(cashflow_json) - - subs = json.loads(load_fixture("get_subscription_details.json", DOMAIN)) + subscription_details = MonarchSubscription( + json.loads(load_fixture("get_subscription_details.json", DOMAIN)) + ) with ( patch( @@ -60,19 +65,8 @@ def mock_config_api() -> Generator[AsyncMock]: 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": { - "id": "123456789", - "paymentSource": "STRIPE", - "referralCode": "go3dpvrdmw", - "isOnFreeTrial": False, - "hasPremiumEntitlement": True, - "__typename": "HouseholdSubscription", - } - } - ) + instance.get_subscription_details = AsyncMock(return_value=subscription_details) instance.get_accounts = AsyncMock(return_value=account_data) instance.get_cashflow_summary = AsyncMock(return_value=cashflow_summary) - instance.get_subscription_details = AsyncMock(return_value=subs) + instance.get_subscription_details = AsyncMock(return_value=subscription_details) yield mock_class From 9cdda9d89be086ade4cc3983e9eb8d1ee5a67ba4 Mon Sep 17 00:00:00 2001 From: Jeff Stein Date: Fri, 6 Sep 2024 14:03:19 -0600 Subject: [PATCH 48/67] the last thing --- homeassistant/components/monarchmoney/config_flow.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/monarchmoney/config_flow.py b/homeassistant/components/monarchmoney/config_flow.py index bd7cdfe64f8dee..0cb077a4b56b54 100644 --- a/homeassistant/components/monarchmoney/config_flow.py +++ b/homeassistant/components/monarchmoney/config_flow.py @@ -142,7 +142,7 @@ async def async_step_user( self._abort_if_unique_id_configured() return self.async_create_entry( - title=info["title"], + title="Monarch Money", data={CONF_TOKEN: info[CONF_TOKEN]}, ) return self.async_show_form( From 81ae87dceb2c4f9a6266eaf9f862af60c79b218b Mon Sep 17 00:00:00 2001 From: Jeff Stein Date: Fri, 6 Sep 2024 16:09:05 -0600 Subject: [PATCH 49/67] update domain name --- homeassistant/components/monarchmoney/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/monarchmoney/const.py b/homeassistant/components/monarchmoney/const.py index d15ac09d153e3f..f450f123179510 100644 --- a/homeassistant/components/monarchmoney/const.py +++ b/homeassistant/components/monarchmoney/const.py @@ -2,7 +2,7 @@ import logging -DOMAIN = "monarchmoney" +DOMAIN = "monarch_money" LOGGER = logging.getLogger(__package__) From 3fdce2e746dc2453912a01b6a9a030b517a53be8 Mon Sep 17 00:00:00 2001 From: Jeff Stein Date: Sat, 7 Sep 2024 07:33:31 -0600 Subject: [PATCH 50/67] i dont know what is in this commit --- CODEOWNERS | 4 ++-- homeassistant/generated/config_flows.py | 2 +- homeassistant/generated/integrations.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/CODEOWNERS b/CODEOWNERS index 6e1227a8fb675a..c25cef4073f707 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -922,8 +922,8 @@ build.json @home-assistant/supervisor /tests/components/modern_forms/ @wonderslug /homeassistant/components/moehlenhoff_alpha2/ @j-a-n /tests/components/moehlenhoff_alpha2/ @j-a-n -/homeassistant/components/monarchmoney/ @jeeftor -/tests/components/monarchmoney/ @jeeftor +/homeassistant/components/monarch_money/ @jeeftor +/tests/components/monarch_money/ @jeeftor /homeassistant/components/monoprice/ @etsinko @OnFreund /tests/components/monoprice/ @etsinko @OnFreund /homeassistant/components/monzo/ @jakemartin-icl diff --git a/homeassistant/generated/config_flows.py b/homeassistant/generated/config_flows.py index 3d94efef59fc6e..37811eaa32195c 100644 --- a/homeassistant/generated/config_flows.py +++ b/homeassistant/generated/config_flows.py @@ -368,7 +368,7 @@ "modem_callerid", "modern_forms", "moehlenhoff_alpha2", - "monarchmoney", + "monarch_money", "monoprice", "monzo", "moon", diff --git a/homeassistant/generated/integrations.json b/homeassistant/generated/integrations.json index 09c3cb65dfbbbc..1f1246154446b5 100644 --- a/homeassistant/generated/integrations.json +++ b/homeassistant/generated/integrations.json @@ -3796,7 +3796,7 @@ "config_flow": false, "iot_class": "local_polling" }, - "monarchmoney": { + "monarch_money": { "name": "Monarch Money", "integration_type": "hub", "config_flow": true, diff --git a/requirements_all.txt b/requirements_all.txt index 8a26b2e4f9e591..7698132ffaaa0e 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2854,7 +2854,7 @@ twilio==6.32.0 # homeassistant.components.twitch twitchAPI==4.2.1 -# homeassistant.components.monarchmoney +# homeassistant.components.monarch_money typedmonarchmoney==0.2.3 # homeassistant.components.ukraine_alarm diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 61081499b023f9..3ff0638d58e047 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -2255,7 +2255,7 @@ twilio==6.32.0 # homeassistant.components.twitch twitchAPI==4.2.1 -# homeassistant.components.monarchmoney +# homeassistant.components.monarch_money typedmonarchmoney==0.2.3 # homeassistant.components.ukraine_alarm From 84474070a1ef153f28886c5818285ceff24f90a6 Mon Sep 17 00:00:00 2001 From: Jeff Stein Date: Sun, 8 Sep 2024 17:47:27 -0600 Subject: [PATCH 51/67] The Great Renaming --- .../{monarchmoney => monarch_money}/__init__.py | 0 .../{monarchmoney => monarch_money}/config_flow.py | 0 .../components/{monarchmoney => monarch_money}/const.py | 0 .../{monarchmoney => monarch_money}/coordinator.py | 0 .../components/{monarchmoney => monarch_money}/entity.py | 0 .../{monarchmoney => monarch_money}/manifest.json | 2 +- .../components/{monarchmoney => monarch_money}/sensor.py | 0 .../{monarchmoney => monarch_money}/strings.json | 0 .../{monarchmoney => monarch_money}/__init__.py | 0 .../{monarchmoney => monarch_money}/conftest.py | 8 ++++---- .../fixtures/get_accounts.json | 0 .../fixtures/get_cashflow_summary.json | 0 .../fixtures/get_subscription_details.json | 0 .../{monarchmoney => monarch_money}/test_config_flow.py | 2 +- .../{monarchmoney => monarch_money}/test_sensor.py | 2 +- 15 files changed, 7 insertions(+), 7 deletions(-) rename homeassistant/components/{monarchmoney => monarch_money}/__init__.py (100%) rename homeassistant/components/{monarchmoney => monarch_money}/config_flow.py (100%) rename homeassistant/components/{monarchmoney => monarch_money}/const.py (100%) rename homeassistant/components/{monarchmoney => monarch_money}/coordinator.py (100%) rename homeassistant/components/{monarchmoney => monarch_money}/entity.py (100%) rename homeassistant/components/{monarchmoney => monarch_money}/manifest.json (89%) rename homeassistant/components/{monarchmoney => monarch_money}/sensor.py (100%) rename homeassistant/components/{monarchmoney => monarch_money}/strings.json (100%) rename tests/components/{monarchmoney => monarch_money}/__init__.py (100%) rename tests/components/{monarchmoney => monarch_money}/conftest.py (86%) rename tests/components/{monarchmoney => monarch_money}/fixtures/get_accounts.json (100%) rename tests/components/{monarchmoney => monarch_money}/fixtures/get_cashflow_summary.json (100%) rename tests/components/{monarchmoney => monarch_money}/fixtures/get_subscription_details.json (100%) rename tests/components/{monarchmoney => monarch_money}/test_config_flow.py (98%) rename tests/components/{monarchmoney => monarch_money}/test_sensor.py (89%) diff --git a/homeassistant/components/monarchmoney/__init__.py b/homeassistant/components/monarch_money/__init__.py similarity index 100% rename from homeassistant/components/monarchmoney/__init__.py rename to homeassistant/components/monarch_money/__init__.py diff --git a/homeassistant/components/monarchmoney/config_flow.py b/homeassistant/components/monarch_money/config_flow.py similarity index 100% rename from homeassistant/components/monarchmoney/config_flow.py rename to homeassistant/components/monarch_money/config_flow.py diff --git a/homeassistant/components/monarchmoney/const.py b/homeassistant/components/monarch_money/const.py similarity index 100% rename from homeassistant/components/monarchmoney/const.py rename to homeassistant/components/monarch_money/const.py diff --git a/homeassistant/components/monarchmoney/coordinator.py b/homeassistant/components/monarch_money/coordinator.py similarity index 100% rename from homeassistant/components/monarchmoney/coordinator.py rename to homeassistant/components/monarch_money/coordinator.py diff --git a/homeassistant/components/monarchmoney/entity.py b/homeassistant/components/monarch_money/entity.py similarity index 100% rename from homeassistant/components/monarchmoney/entity.py rename to homeassistant/components/monarch_money/entity.py diff --git a/homeassistant/components/monarchmoney/manifest.json b/homeassistant/components/monarch_money/manifest.json similarity index 89% rename from homeassistant/components/monarchmoney/manifest.json rename to homeassistant/components/monarch_money/manifest.json index 080b46d2cba7a8..2d71da614a8baa 100644 --- a/homeassistant/components/monarchmoney/manifest.json +++ b/homeassistant/components/monarch_money/manifest.json @@ -1,5 +1,5 @@ { - "domain": "monarchmoney", + "domain": "monarch_money", "name": "Monarch Money", "codeowners": ["@jeeftor"], "config_flow": true, diff --git a/homeassistant/components/monarchmoney/sensor.py b/homeassistant/components/monarch_money/sensor.py similarity index 100% rename from homeassistant/components/monarchmoney/sensor.py rename to homeassistant/components/monarch_money/sensor.py diff --git a/homeassistant/components/monarchmoney/strings.json b/homeassistant/components/monarch_money/strings.json similarity index 100% rename from homeassistant/components/monarchmoney/strings.json rename to homeassistant/components/monarch_money/strings.json diff --git a/tests/components/monarchmoney/__init__.py b/tests/components/monarch_money/__init__.py similarity index 100% rename from tests/components/monarchmoney/__init__.py rename to tests/components/monarch_money/__init__.py diff --git a/tests/components/monarchmoney/conftest.py b/tests/components/monarch_money/conftest.py similarity index 86% rename from tests/components/monarchmoney/conftest.py rename to tests/components/monarch_money/conftest.py index db51bfc66e05fe..180317b72d2087 100644 --- a/tests/components/monarchmoney/conftest.py +++ b/tests/components/monarch_money/conftest.py @@ -12,7 +12,7 @@ MonarchSubscription, ) -from homeassistant.components.monarchmoney.const import DOMAIN +from homeassistant.components.monarch_money.const import DOMAIN from homeassistant.const import CONF_TOKEN from tests.common import MockConfigEntry, load_fixture @@ -22,7 +22,7 @@ def mock_setup_entry() -> Generator[AsyncMock]: """Override async_setup_entry.""" with patch( - "homeassistant.components.monarchmoney.async_setup_entry", return_value=True + "homeassistant.components.monarch_money.async_setup_entry", return_value=True ) as mock_setup_entry: yield mock_setup_entry @@ -54,11 +54,11 @@ def mock_config_api() -> Generator[AsyncMock]: with ( patch( - "homeassistant.components.monarchmoney.config_flow.TypedMonarchMoney", + "homeassistant.components.monarch_money.config_flow.TypedMonarchMoney", autospec=True, ) as mock_class, patch( - "homeassistant.components.monarchmoney.TypedMonarchMoney", new=mock_class + "homeassistant.components.monarch_money.TypedMonarchMoney", new=mock_class ), ): instance = mock_class.return_value diff --git a/tests/components/monarchmoney/fixtures/get_accounts.json b/tests/components/monarch_money/fixtures/get_accounts.json similarity index 100% rename from tests/components/monarchmoney/fixtures/get_accounts.json rename to tests/components/monarch_money/fixtures/get_accounts.json diff --git a/tests/components/monarchmoney/fixtures/get_cashflow_summary.json b/tests/components/monarch_money/fixtures/get_cashflow_summary.json similarity index 100% rename from tests/components/monarchmoney/fixtures/get_cashflow_summary.json rename to tests/components/monarch_money/fixtures/get_cashflow_summary.json diff --git a/tests/components/monarchmoney/fixtures/get_subscription_details.json b/tests/components/monarch_money/fixtures/get_subscription_details.json similarity index 100% rename from tests/components/monarchmoney/fixtures/get_subscription_details.json rename to tests/components/monarch_money/fixtures/get_subscription_details.json diff --git a/tests/components/monarchmoney/test_config_flow.py b/tests/components/monarch_money/test_config_flow.py similarity index 98% rename from tests/components/monarchmoney/test_config_flow.py rename to tests/components/monarch_money/test_config_flow.py index d3903a37073c5a..a83eaf8b9395b7 100644 --- a/tests/components/monarchmoney/test_config_flow.py +++ b/tests/components/monarch_money/test_config_flow.py @@ -5,7 +5,7 @@ from monarchmoney import LoginFailedException, RequireMFAException from homeassistant import config_entries -from homeassistant.components.monarchmoney.const import CONF_MFA_CODE, DOMAIN +from homeassistant.components.monarch_money.const import CONF_MFA_CODE, DOMAIN from homeassistant.const import CONF_EMAIL, CONF_PASSWORD, CONF_TOKEN from homeassistant.core import HomeAssistant from homeassistant.data_entry_flow import FlowResultType diff --git a/tests/components/monarchmoney/test_sensor.py b/tests/components/monarch_money/test_sensor.py similarity index 89% rename from tests/components/monarchmoney/test_sensor.py rename to tests/components/monarch_money/test_sensor.py index 2e513d6228abff..aac1eaefb2de8f 100644 --- a/tests/components/monarchmoney/test_sensor.py +++ b/tests/components/monarch_money/test_sensor.py @@ -21,7 +21,7 @@ async def test_all_entities( mock_config_api: AsyncMock, ) -> None: """Test all entities.""" - with patch("homeassistant.components.monarchmoney.PLATFORMS", [Platform.SENSOR]): + with patch("homeassistant.components.monarch_money.PLATFORMS", [Platform.SENSOR]): await setup_integration(hass, mock_config_entry) await snapshot_platform(hass, entity_registry, snapshot, mock_config_entry.entry_id) From 7efd4be7a370d5e73e743de4d47b318a128f646a Mon Sep 17 00:00:00 2001 From: Jeff Stein Date: Mon, 9 Sep 2024 06:46:34 -0600 Subject: [PATCH 52/67] Moving to dict style accounting - as per request --- .../components/monarch_money/coordinator.py | 18 ++--- .../components/monarch_money/entity.py | 6 +- .../components/monarch_money/icons.json | 10 +++ .../components/monarch_money/manifest.json | 2 +- .../components/monarch_money/sensor.py | 67 +++++++------------ .../components/monarch_money/strings.json | 3 - requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/monarch_money/conftest.py | 7 ++ .../monarch_money/test_config_flow.py | 6 +- 10 files changed, 60 insertions(+), 63 deletions(-) create mode 100644 homeassistant/components/monarch_money/icons.json diff --git a/homeassistant/components/monarch_money/coordinator.py b/homeassistant/components/monarch_money/coordinator.py index a5a2723017dcac..61dee566466291 100644 --- a/homeassistant/components/monarch_money/coordinator.py +++ b/homeassistant/components/monarch_money/coordinator.py @@ -26,7 +26,7 @@ class MonarchData: """Data class to hold monarch data.""" - account_data: list[MonarchAccount] + account_data: dict[int, MonarchAccount] cashflow_summary: MonarchCashflowSummary @@ -34,7 +34,7 @@ class MonarchMoneyDataUpdateCoordinator(DataUpdateCoordinator[MonarchData]): """Data update coordinator for Monarch Money.""" config_entry: ConfigEntry - subscription_id: str + subscription_id: int def __init__( self, @@ -64,7 +64,8 @@ async def _async_update_data(self) -> MonarchData: """Fetch data for all accounts.""" account_data, cashflow_summary = await asyncio.gather( - self.client.get_accounts(), self.client.get_cashflow_summary() + self.client.get_accounts_as_dict_with_id_key(), + self.client.get_cashflow_summary(), ) return MonarchData(account_data=account_data, cashflow_summary=cashflow_summary) @@ -77,8 +78,7 @@ def cashflow_summary(self) -> MonarchCashflowSummary: @property def accounts(self) -> list[MonarchAccount]: """Return accounts.""" - - return self.data.account_data + return list(self.data.account_data.values()) @property def value_accounts(self) -> list[MonarchAccount]: @@ -90,9 +90,9 @@ def balance_accounts(self) -> list[MonarchAccount]: """Return accounts that aren't assets.""" return [x for x in self.accounts if x.is_balance_account] - def get_account_for_id(self, account_id: str) -> MonarchAccount: + def get_account_for_id(self, account_id: int) -> MonarchAccount: """Get account for id.""" - for account in self.data.account_data: - if account.id == account_id: - return account + if account_id in self.data.account_data: + return self.data.account_data[account_id] + raise ValueError(f"Account with id {account_id} not found") diff --git a/homeassistant/components/monarch_money/entity.py b/homeassistant/components/monarch_money/entity.py index 969995ceaeaeb9..7281d6dfe84c27 100644 --- a/homeassistant/components/monarch_money/entity.py +++ b/homeassistant/components/monarch_money/entity.py @@ -31,7 +31,7 @@ def __init__( ) self.entity_description = description self._attr_device_info = DeviceInfo( - identifiers={(DOMAIN, coordinator.subscription_id)}, + identifiers={(DOMAIN, str(coordinator.subscription_id))}, name="Cashflow", ) @@ -62,7 +62,7 @@ def __init__( f"{coordinator.subscription_id}_{account.id}_{description.translation_key}" ) self._attr_device_info = DeviceInfo( - identifiers={(DOMAIN, account.id)}, + identifiers={(DOMAIN, str(account.id))}, name=f"{account.institution_name} {account.name}", entry_type=DeviceEntryType.SERVICE, manufacturer=account.data_provider, @@ -74,7 +74,7 @@ def __init__( def available(self) -> bool: """Return if entity is available.""" return super().available and ( - self._account_id in [x.id for x in self.coordinator.data.account_data] + self._account_id in self.coordinator.data.account_data ) @property diff --git a/homeassistant/components/monarch_money/icons.json b/homeassistant/components/monarch_money/icons.json new file mode 100644 index 00000000000000..95c5eb3cca4d09 --- /dev/null +++ b/homeassistant/components/monarch_money/icons.json @@ -0,0 +1,10 @@ +{ + "entity": { + "sensor": { + "sum_income": { "default": "mdi:cash-plus" }, + "sum_expense": { "default": "mdi:cash-minus" }, + "savings": { "default": "mdi:piggy-bank-outline" }, + "savings_rate": { "default": "mdi:cash-sync" } + } + } +} diff --git a/homeassistant/components/monarch_money/manifest.json b/homeassistant/components/monarch_money/manifest.json index 2d71da614a8baa..e89b4d1c764db4 100644 --- a/homeassistant/components/monarch_money/manifest.json +++ b/homeassistant/components/monarch_money/manifest.json @@ -5,5 +5,5 @@ "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/monarchmoney", "iot_class": "cloud_polling", - "requirements": ["typedmonarchmoney==0.2.3"] + "requirements": ["typedmonarchmoney==0.2.6"] } diff --git a/homeassistant/components/monarch_money/sensor.py b/homeassistant/components/monarch_money/sensor.py index 47d2fd05017ddb..1cdd8de60b0955 100644 --- a/homeassistant/components/monarch_money/sensor.py +++ b/homeassistant/components/monarch_money/sensor.py @@ -22,12 +22,7 @@ @dataclass(frozen=True, kw_only=True) -class MonarchMoneySensorEntityDescription(SensorEntityDescription): - """Base common class.""" - - -@dataclass(frozen=True, kw_only=True) -class MonarchMoneyAccountSensorEntityDescription(MonarchMoneySensorEntityDescription): +class MonarchMoneyAccountSensorEntityDescription(SensorEntityDescription): """Describe an account sensor entity.""" value_fn: Callable[[MonarchAccount], StateType | datetime] @@ -35,10 +30,10 @@ class MonarchMoneyAccountSensorEntityDescription(MonarchMoneySensorEntityDescrip @dataclass(frozen=True, kw_only=True) -class MonarchMoneyCashflowSensorEntityDescription(MonarchMoneySensorEntityDescription): +class MonarchMoneyCashflowSensorEntityDescription(SensorEntityDescription): """Describe a cashflow sensor entity.""" - summary_fn: Callable[[MonarchCashflowSummary], StateType] | None + summary_fn: Callable[[MonarchCashflowSummary], StateType] # These sensors include assets like a boat that might have value @@ -85,7 +80,6 @@ class MonarchMoneyCashflowSensorEntityDescription(MonarchMoneySensorEntityDescri state_class=SensorStateClass.TOTAL, device_class=SensorDeviceClass.MONETARY, native_unit_of_measurement=CURRENCY_DOLLAR, - icon="mdi:cash-plus", ), MonarchMoneyCashflowSensorEntityDescription( key="sum_expense", @@ -94,7 +88,6 @@ class MonarchMoneyCashflowSensorEntityDescription(MonarchMoneySensorEntityDescri state_class=SensorStateClass.TOTAL, device_class=SensorDeviceClass.MONETARY, native_unit_of_measurement=CURRENCY_DOLLAR, - icon="mdi:cash-minus", ), MonarchMoneyCashflowSensorEntityDescription( key="savings", @@ -103,7 +96,6 @@ class MonarchMoneyCashflowSensorEntityDescription(MonarchMoneySensorEntityDescri state_class=SensorStateClass.TOTAL, device_class=SensorDeviceClass.MONETARY, native_unit_of_measurement=CURRENCY_DOLLAR, - icon="mdi:piggy-bank-outline", ), MonarchMoneyCashflowSensorEntityDescription( key="savings_rate", @@ -111,7 +103,6 @@ class MonarchMoneyCashflowSensorEntityDescription(MonarchMoneySensorEntityDescri summary_fn=lambda summary: summary.savings_rate * 100, suggested_display_precision=1, native_unit_of_measurement=PERCENTAGE, - icon="mdi:cash-sync", ), ) @@ -132,37 +123,31 @@ async def async_setup_entry( for sensor_description in MONARCH_CASHFLOW_SENSORS ] entity_list.extend( - [ - MonarchMoneySensor( - mm_coordinator, - sensor_description, - account, - ) - for account in mm_coordinator.balance_accounts - for sensor_description in MONARCH_MONEY_SENSORS - ] + MonarchMoneySensor( + mm_coordinator, + sensor_description, + account, + ) + for account in mm_coordinator.balance_accounts + for sensor_description in MONARCH_MONEY_SENSORS ) entity_list.extend( - [ - MonarchMoneySensor( - mm_coordinator, - sensor_description, - account, - ) - for account in mm_coordinator.accounts - for sensor_description in MONARCH_MONEY_AGE_SENSORS - ] + MonarchMoneySensor( + mm_coordinator, + sensor_description, + account, + ) + for account in mm_coordinator.accounts + for sensor_description in MONARCH_MONEY_AGE_SENSORS ) entity_list.extend( - [ - MonarchMoneySensor( - mm_coordinator, - sensor_description, - account, - ) - for account in mm_coordinator.value_accounts - for sensor_description in MONARCH_MONEY_VALUE_SENSORS - ] + MonarchMoneySensor( + mm_coordinator, + sensor_description, + account, + ) + for account in mm_coordinator.value_accounts + for sensor_description in MONARCH_MONEY_VALUE_SENSORS ) async_add_entities(entity_list) @@ -176,9 +161,7 @@ class MonarchMoneyCashFlowSensor(MonarchMoneyCashFlowEntity, SensorEntity): @property def native_value(self) -> StateType | datetime: """Return the state.""" - if self.entity_description.summary_fn is not None: - return self.entity_description.summary_fn(self.summary_data) - return None + return self.entity_description.summary_fn(self.summary_data) class MonarchMoneySensor(MonarchMoneyAccountEntity, SensorEntity): diff --git a/homeassistant/components/monarch_money/strings.json b/homeassistant/components/monarch_money/strings.json index 5622a0454c0f4a..ccca2bee54d5a6 100644 --- a/homeassistant/components/monarch_money/strings.json +++ b/homeassistant/components/monarch_money/strings.json @@ -40,9 +40,6 @@ }, "savings_rate": { "name": "Savings rate" - }, - "created": { - "name": "First sync" } } } diff --git a/requirements_all.txt b/requirements_all.txt index 7698132ffaaa0e..c61a24c9d1654b 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2855,7 +2855,7 @@ twilio==6.32.0 twitchAPI==4.2.1 # homeassistant.components.monarch_money -typedmonarchmoney==0.2.3 +typedmonarchmoney==0.2.6 # homeassistant.components.ukraine_alarm uasiren==0.0.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 3ff0638d58e047..61c74ea4ad7ee1 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -2256,7 +2256,7 @@ twilio==6.32.0 twitchAPI==4.2.1 # homeassistant.components.monarch_money -typedmonarchmoney==0.2.3 +typedmonarchmoney==0.2.6 # homeassistant.components.ukraine_alarm uasiren==0.0.1 diff --git a/tests/components/monarch_money/conftest.py b/tests/components/monarch_money/conftest.py index 180317b72d2087..4a9d5dde2c6a51 100644 --- a/tests/components/monarch_money/conftest.py +++ b/tests/components/monarch_money/conftest.py @@ -44,6 +44,10 @@ def mock_config_api() -> Generator[AsyncMock]: account_json: dict[str, Any] = json.loads(load_fixture("get_accounts.json", DOMAIN)) account_data = [MonarchAccount(data) for data in account_json["accounts"]] + account_data_dict = { + int(acc["id"]): MonarchAccount(acc) for acc in account_json["accounts"] + } + cashflow_json: dict[str, Any] = json.loads( load_fixture("get_cashflow_summary.json", DOMAIN) ) @@ -67,6 +71,9 @@ def mock_config_api() -> Generator[AsyncMock]: instance.multi_factor_authenticate = AsyncMock(return_value=None) instance.get_subscription_details = AsyncMock(return_value=subscription_details) instance.get_accounts = AsyncMock(return_value=account_data) + instance.get_accounts_as_dict_with_id_key = AsyncMock( + return_value=account_data_dict + ) instance.get_cashflow_summary = AsyncMock(return_value=cashflow_summary) instance.get_subscription_details = AsyncMock(return_value=subscription_details) yield mock_class diff --git a/tests/components/monarch_money/test_config_flow.py b/tests/components/monarch_money/test_config_flow.py index a83eaf8b9395b7..3987c993ab647b 100644 --- a/tests/components/monarch_money/test_config_flow.py +++ b/tests/components/monarch_money/test_config_flow.py @@ -35,7 +35,7 @@ async def test_form_simple( assert result["data"] == { CONF_TOKEN: "mocked_token", } - assert result["context"]["unique_id"] == "222260252323873333" + assert result["context"]["unique_id"] == 222260252323873333 assert len(mock_setup_entry.mock_calls) == 1 @@ -81,7 +81,7 @@ async def test_form_invalid_auth( assert result["data"] == { CONF_TOKEN: "mocked_token", } - assert result["context"]["unique_id"] == "222260252323873333" + assert result["context"]["unique_id"] == 222260252323873333 assert len(mock_setup_entry.mock_calls) == 1 @@ -140,6 +140,6 @@ async def test_form_mfa( assert result["data"] == { CONF_TOKEN: "mocked_token", } - assert result["context"]["unique_id"] == "222260252323873333" + assert result["context"]["unique_id"] == 222260252323873333 assert len(mock_setup_entry.mock_calls) == 1 From 2280de7291ba4316db236d107ff4f2ba7a7e2046 Mon Sep 17 00:00:00 2001 From: Jeff Stein Date: Mon, 9 Sep 2024 08:46:41 -0600 Subject: [PATCH 53/67] updating backing deps --- homeassistant/components/monarch_money/manifest.json | 2 +- homeassistant/generated/integrations.json | 6 ------ requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 4 files changed, 3 insertions(+), 9 deletions(-) diff --git a/homeassistant/components/monarch_money/manifest.json b/homeassistant/components/monarch_money/manifest.json index e89b4d1c764db4..b593e640015870 100644 --- a/homeassistant/components/monarch_money/manifest.json +++ b/homeassistant/components/monarch_money/manifest.json @@ -5,5 +5,5 @@ "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/monarchmoney", "iot_class": "cloud_polling", - "requirements": ["typedmonarchmoney==0.2.6"] + "requirements": ["typedmonarchmoney==0.2.7"] } diff --git a/homeassistant/generated/integrations.json b/homeassistant/generated/integrations.json index 1f1246154446b5..ea3b28d3b59bc2 100644 --- a/homeassistant/generated/integrations.json +++ b/homeassistant/generated/integrations.json @@ -3790,12 +3790,6 @@ "config_flow": true, "iot_class": "local_push" }, - "mold_indicator": { - "name": "Mold Indicator", - "integration_type": "hub", - "config_flow": false, - "iot_class": "local_polling" - }, "monarch_money": { "name": "Monarch Money", "integration_type": "hub", diff --git a/requirements_all.txt b/requirements_all.txt index c61a24c9d1654b..13bd16ff05e872 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2855,7 +2855,7 @@ twilio==6.32.0 twitchAPI==4.2.1 # homeassistant.components.monarch_money -typedmonarchmoney==0.2.6 +typedmonarchmoney==0.2.7 # homeassistant.components.ukraine_alarm uasiren==0.0.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 61c74ea4ad7ee1..051765e9bb1b16 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -2256,7 +2256,7 @@ twilio==6.32.0 twitchAPI==4.2.1 # homeassistant.components.monarch_money -typedmonarchmoney==0.2.6 +typedmonarchmoney==0.2.7 # homeassistant.components.ukraine_alarm uasiren==0.0.1 From 58c0e055c6274f06b477e465d88db3d7f99ebb58 Mon Sep 17 00:00:00 2001 From: Jeef Date: Tue, 10 Sep 2024 07:41:45 -0600 Subject: [PATCH 54/67] Update homeassistant/components/monarch_money/entity.py Co-authored-by: Joost Lekkerkerker --- homeassistant/components/monarch_money/entity.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/homeassistant/components/monarch_money/entity.py b/homeassistant/components/monarch_money/entity.py index 7281d6dfe84c27..e54723b1ba3dfd 100644 --- a/homeassistant/components/monarch_money/entity.py +++ b/homeassistant/components/monarch_money/entity.py @@ -80,7 +80,4 @@ def available(self) -> bool: @property def account_data(self) -> MonarchAccount: """Return the account data.""" - if account := self.coordinator.get_account_for_id(self._account_id): - return account - - raise ValueError("Account not found") + return self.coordinator.data[self._account_id] From e658a55597ce03828d71a618d22373236083d5c0 Mon Sep 17 00:00:00 2001 From: Jeef Date: Tue, 10 Sep 2024 07:46:19 -0600 Subject: [PATCH 55/67] Update tests/components/monarch_money/test_config_flow.py Co-authored-by: Joost Lekkerkerker --- tests/components/monarch_money/test_config_flow.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/components/monarch_money/test_config_flow.py b/tests/components/monarch_money/test_config_flow.py index 3987c993ab647b..cb7eec048efa5f 100644 --- a/tests/components/monarch_money/test_config_flow.py +++ b/tests/components/monarch_money/test_config_flow.py @@ -140,6 +140,6 @@ async def test_form_mfa( assert result["data"] == { CONF_TOKEN: "mocked_token", } - assert result["context"]["unique_id"] == 222260252323873333 + assert result["result"].unique_id == 222260252323873333 assert len(mock_setup_entry.mock_calls) == 1 From 11b20b10302845bf4bc64467aaaf10dda88fc664 Mon Sep 17 00:00:00 2001 From: Jeef Date: Tue, 10 Sep 2024 07:46:32 -0600 Subject: [PATCH 56/67] Update tests/components/monarch_money/test_config_flow.py Co-authored-by: Joost Lekkerkerker --- tests/components/monarch_money/test_config_flow.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/components/monarch_money/test_config_flow.py b/tests/components/monarch_money/test_config_flow.py index cb7eec048efa5f..d7e2517bb1fab1 100644 --- a/tests/components/monarch_money/test_config_flow.py +++ b/tests/components/monarch_money/test_config_flow.py @@ -30,7 +30,7 @@ async def test_form_simple( ) await hass.async_block_till_done() - assert result["type"] == FlowResultType.CREATE_ENTRY + assert result["type"] is FlowResultType.CREATE_ENTRY assert result["title"] == "Monarch Money" assert result["data"] == { CONF_TOKEN: "mocked_token", From 8b9a2ebe069c980a51070698341f993c06bfe338 Mon Sep 17 00:00:00 2001 From: Jeef Date: Tue, 10 Sep 2024 07:46:47 -0600 Subject: [PATCH 57/67] Update tests/components/monarch_money/test_config_flow.py Co-authored-by: Joost Lekkerkerker --- tests/components/monarch_money/test_config_flow.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/components/monarch_money/test_config_flow.py b/tests/components/monarch_money/test_config_flow.py index d7e2517bb1fab1..25663cd5ba0137 100644 --- a/tests/components/monarch_money/test_config_flow.py +++ b/tests/components/monarch_money/test_config_flow.py @@ -16,9 +16,9 @@ async def test_form_simple( ) -> None: """Test we get the form.""" result = await hass.config_entries.flow.async_init( - DOMAIN, context={"source": config_entries.SOURCE_USER} + DOMAIN, context={"source": SOURCE_USER} ) - assert result["type"] == FlowResultType.FORM + assert result["type"] is FlowResultType.FORM assert result["errors"] == {} result = await hass.config_entries.flow.async_configure( From aaa028a08dc3d04b4c500d075ffde465cac6e800 Mon Sep 17 00:00:00 2001 From: Jeef Date: Tue, 10 Sep 2024 07:47:45 -0600 Subject: [PATCH 58/67] Update homeassistant/components/monarch_money/sensor.py Co-authored-by: Joost Lekkerkerker --- homeassistant/components/monarch_money/sensor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/monarch_money/sensor.py b/homeassistant/components/monarch_money/sensor.py index 1cdd8de60b0955..1e0e71652bd674 100644 --- a/homeassistant/components/monarch_money/sensor.py +++ b/homeassistant/components/monarch_money/sensor.py @@ -159,7 +159,7 @@ class MonarchMoneyCashFlowSensor(MonarchMoneyCashFlowEntity, SensorEntity): entity_description: MonarchMoneyCashflowSensorEntityDescription @property - def native_value(self) -> StateType | datetime: + def native_value(self) -> StateType: """Return the state.""" return self.entity_description.summary_fn(self.summary_data) From 8e53908ebf5f6c0b85925a12677eba550aa91a02 Mon Sep 17 00:00:00 2001 From: Jeff Stein Date: Tue, 10 Sep 2024 07:54:50 -0600 Subject: [PATCH 59/67] some changes --- .../components/monarch_money/coordinator.py | 12 ++++++------ homeassistant/components/monarch_money/entity.py | 2 +- homeassistant/components/monarch_money/sensor.py | 4 +--- tests/components/monarch_money/test_config_flow.py | 1 + 4 files changed, 9 insertions(+), 10 deletions(-) diff --git a/homeassistant/components/monarch_money/coordinator.py b/homeassistant/components/monarch_money/coordinator.py index 61dee566466291..0ecf5dfed3af4a 100644 --- a/homeassistant/components/monarch_money/coordinator.py +++ b/homeassistant/components/monarch_money/coordinator.py @@ -90,9 +90,9 @@ def balance_accounts(self) -> list[MonarchAccount]: """Return accounts that aren't assets.""" return [x for x in self.accounts if x.is_balance_account] - def get_account_for_id(self, account_id: int) -> MonarchAccount: - """Get account for id.""" - if account_id in self.data.account_data: - return self.data.account_data[account_id] - - raise ValueError(f"Account with id {account_id} not found") + # def get_account_for_id(self, account_id: int) -> MonarchAccount: + # """Get account for id.""" + # if account_id in self.data.account_data: + # return self.data.account_data[account_id] + # + # raise ValueError(f"Account with id {account_id} not found") diff --git a/homeassistant/components/monarch_money/entity.py b/homeassistant/components/monarch_money/entity.py index e54723b1ba3dfd..49a24385782564 100644 --- a/homeassistant/components/monarch_money/entity.py +++ b/homeassistant/components/monarch_money/entity.py @@ -80,4 +80,4 @@ def available(self) -> bool: @property def account_data(self) -> MonarchAccount: """Return the account data.""" - return self.coordinator.data[self._account_id] + return self.coordinator.data.account_data[self._account_id] diff --git a/homeassistant/components/monarch_money/sensor.py b/homeassistant/components/monarch_money/sensor.py index 1e0e71652bd674..d17b86b0e1d64a 100644 --- a/homeassistant/components/monarch_money/sensor.py +++ b/homeassistant/components/monarch_money/sensor.py @@ -172,9 +172,7 @@ class MonarchMoneySensor(MonarchMoneyAccountEntity, SensorEntity): @property def native_value(self) -> StateType | datetime | None: """Return the state.""" - if self.entity_description.value_fn is not None: - return self.entity_description.value_fn(self.account_data) - return None + return self.entity_description.value_fn(self.account_data) @property def entity_picture(self) -> str | None: diff --git a/tests/components/monarch_money/test_config_flow.py b/tests/components/monarch_money/test_config_flow.py index 25663cd5ba0137..6ec0bcad6b158d 100644 --- a/tests/components/monarch_money/test_config_flow.py +++ b/tests/components/monarch_money/test_config_flow.py @@ -6,6 +6,7 @@ from homeassistant import config_entries from homeassistant.components.monarch_money.const import CONF_MFA_CODE, DOMAIN +from homeassistant.config_entries import SOURCE_USER from homeassistant.const import CONF_EMAIL, CONF_PASSWORD, CONF_TOKEN from homeassistant.core import HomeAssistant from homeassistant.data_entry_flow import FlowResultType From d54450d00a75a67c12c3162f29bd284fba7c1f1a Mon Sep 17 00:00:00 2001 From: Jeff Stein Date: Tue, 10 Sep 2024 07:56:08 -0600 Subject: [PATCH 60/67] fixing capitalizaton --- homeassistant/components/monarch_money/strings.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/monarch_money/strings.json b/homeassistant/components/monarch_money/strings.json index ccca2bee54d5a6..d7a28940d7a724 100644 --- a/homeassistant/components/monarch_money/strings.json +++ b/homeassistant/components/monarch_money/strings.json @@ -30,13 +30,13 @@ }, "sum_income": { - "name": "Income Year to Date" + "name": "Income year to date" }, "sum_expense": { - "name": "Expense Year to Date" + "name": "Expense year to date" }, "savings": { - "name": "Savings Year to Date" + "name": "Savings year to date" }, "savings_rate": { "name": "Savings rate" From a0e44eed044a7763f252c6dec643242746030fec Mon Sep 17 00:00:00 2001 From: Jeff Stein Date: Tue, 10 Sep 2024 08:33:59 -0600 Subject: [PATCH 61/67] test test test --- .../components/monarch_money/coordinator.py | 11 ++--------- .../components/monarch_money/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/monarch_money/conftest.py | 8 ++++---- tests/components/monarch_money/test_config_flow.py | 13 ++++++++++--- tests/components/wemo/entity_test_helpers.py | 2 +- 7 files changed, 20 insertions(+), 20 deletions(-) diff --git a/homeassistant/components/monarch_money/coordinator.py b/homeassistant/components/monarch_money/coordinator.py index 0ecf5dfed3af4a..8eb15d448ecc94 100644 --- a/homeassistant/components/monarch_money/coordinator.py +++ b/homeassistant/components/monarch_money/coordinator.py @@ -26,7 +26,7 @@ class MonarchData: """Data class to hold monarch data.""" - account_data: dict[int, MonarchAccount] + account_data: dict[str, MonarchAccount] cashflow_summary: MonarchCashflowSummary @@ -34,7 +34,7 @@ class MonarchMoneyDataUpdateCoordinator(DataUpdateCoordinator[MonarchData]): """Data update coordinator for Monarch Money.""" config_entry: ConfigEntry - subscription_id: int + subscription_id: str def __init__( self, @@ -89,10 +89,3 @@ def value_accounts(self) -> list[MonarchAccount]: def balance_accounts(self) -> list[MonarchAccount]: """Return accounts that aren't assets.""" return [x for x in self.accounts if x.is_balance_account] - - # def get_account_for_id(self, account_id: int) -> MonarchAccount: - # """Get account for id.""" - # if account_id in self.data.account_data: - # return self.data.account_data[account_id] - # - # raise ValueError(f"Account with id {account_id} not found") diff --git a/homeassistant/components/monarch_money/manifest.json b/homeassistant/components/monarch_money/manifest.json index b593e640015870..ed28f825bcfa3b 100644 --- a/homeassistant/components/monarch_money/manifest.json +++ b/homeassistant/components/monarch_money/manifest.json @@ -5,5 +5,5 @@ "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/monarchmoney", "iot_class": "cloud_polling", - "requirements": ["typedmonarchmoney==0.2.7"] + "requirements": ["typedmonarchmoney==0.3.1"] } diff --git a/requirements_all.txt b/requirements_all.txt index 13bd16ff05e872..a5aa2be49378da 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2855,7 +2855,7 @@ twilio==6.32.0 twitchAPI==4.2.1 # homeassistant.components.monarch_money -typedmonarchmoney==0.2.7 +typedmonarchmoney==0.3.1 # homeassistant.components.ukraine_alarm uasiren==0.0.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 051765e9bb1b16..2f39d4ece40e14 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -2256,7 +2256,7 @@ twilio==6.32.0 twitchAPI==4.2.1 # homeassistant.components.monarch_money -typedmonarchmoney==0.2.7 +typedmonarchmoney==0.3.1 # homeassistant.components.ukraine_alarm uasiren==0.0.1 diff --git a/tests/components/monarch_money/conftest.py b/tests/components/monarch_money/conftest.py index 4a9d5dde2c6a51..7d6a965a0090d2 100644 --- a/tests/components/monarch_money/conftest.py +++ b/tests/components/monarch_money/conftest.py @@ -15,7 +15,7 @@ from homeassistant.components.monarch_money.const import DOMAIN from homeassistant.const import CONF_TOKEN -from tests.common import MockConfigEntry, load_fixture +from tests.common import MockConfigEntry, load_fixture, load_json_object_fixture @pytest.fixture @@ -42,10 +42,10 @@ async def mock_config_entry() -> MockConfigEntry: def mock_config_api() -> Generator[AsyncMock]: """Mock the MonarchMoney class.""" - account_json: dict[str, Any] = json.loads(load_fixture("get_accounts.json", DOMAIN)) + account_json: dict[str, Any] = load_json_object_fixture("get_accounts.json", DOMAIN) account_data = [MonarchAccount(data) for data in account_json["accounts"]] - account_data_dict = { - int(acc["id"]): MonarchAccount(acc) for acc in account_json["accounts"] + account_data_dict: dict[str, MonarchAccount] = { + acc["id"]: MonarchAccount(acc) for acc in account_json["accounts"] } cashflow_json: dict[str, Any] = json.loads( diff --git a/tests/components/monarch_money/test_config_flow.py b/tests/components/monarch_money/test_config_flow.py index 6ec0bcad6b158d..5f26229269ea4a 100644 --- a/tests/components/monarch_money/test_config_flow.py +++ b/tests/components/monarch_money/test_config_flow.py @@ -36,10 +36,17 @@ async def test_form_simple( assert result["data"] == { CONF_TOKEN: "mocked_token", } - assert result["context"]["unique_id"] == 222260252323873333 + assert result["context"]["unique_id"] == "222260252323873333" assert len(mock_setup_entry.mock_calls) == 1 +async def test_add_duplicate_entry( + hass: HomeAssistant, mock_setup_entry: AsyncMock, mock_config_api: AsyncMock +) -> None: + """Test duplicate case.""" + assert False + + async def test_form_invalid_auth( hass: HomeAssistant, mock_setup_entry: AsyncMock, mock_config_api: AsyncMock ) -> None: @@ -82,7 +89,7 @@ async def test_form_invalid_auth( assert result["data"] == { CONF_TOKEN: "mocked_token", } - assert result["context"]["unique_id"] == 222260252323873333 + assert result["context"]["unique_id"] == "222260252323873333" assert len(mock_setup_entry.mock_calls) == 1 @@ -141,6 +148,6 @@ async def test_form_mfa( assert result["data"] == { CONF_TOKEN: "mocked_token", } - assert result["result"].unique_id == 222260252323873333 + assert result["result"].unique_id == "222260252323873333" assert len(mock_setup_entry.mock_calls) == 1 diff --git a/tests/components/wemo/entity_test_helpers.py b/tests/components/wemo/entity_test_helpers.py index ff223f8e026ab8..f57dffad6f9cf3 100644 --- a/tests/components/wemo/entity_test_helpers.py +++ b/tests/components/wemo/entity_test_helpers.py @@ -40,7 +40,7 @@ def _perform_async_update(coordinator): """Return a callable method to cause hass to update the state of the entity.""" async def async_callback(): - await coordinator._async_update_data + await coordinator._async_update_data() return async_callback From 2089b7cee1ef04460e33a8a32fb758acf18b7b5e Mon Sep 17 00:00:00 2001 From: Jeff Stein Date: Tue, 10 Sep 2024 14:31:56 -0600 Subject: [PATCH 62/67] Adding dupe test --- .../monarch_money/test_config_flow.py | 34 ++++++++++++++++--- 1 file changed, 29 insertions(+), 5 deletions(-) diff --git a/tests/components/monarch_money/test_config_flow.py b/tests/components/monarch_money/test_config_flow.py index 5f26229269ea4a..08848f6d5fb552 100644 --- a/tests/components/monarch_money/test_config_flow.py +++ b/tests/components/monarch_money/test_config_flow.py @@ -15,7 +15,7 @@ async def test_form_simple( hass: HomeAssistant, mock_setup_entry: AsyncMock, mock_config_api: AsyncMock ) -> None: - """Test we get the form.""" + """Test simple case (no MFA / no errors).""" result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": SOURCE_USER} ) @@ -41,16 +41,40 @@ async def test_form_simple( async def test_add_duplicate_entry( - hass: HomeAssistant, mock_setup_entry: AsyncMock, mock_config_api: AsyncMock + hass: HomeAssistant, + mock_config_entry, + mock_setup_entry: AsyncMock, + mock_config_api: AsyncMock, ) -> None: - """Test duplicate case.""" - assert False + """Test a duplicate error config flow.""" + mock_config_entry.add_to_hass(hass) + + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + assert result["type"] == FlowResultType.FORM + assert result["errors"] == {} + + assert result["type"] is FlowResultType.FORM + assert result["errors"] == {} + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + { + CONF_EMAIL: "test-username", + CONF_PASSWORD: "test-password", + }, + ) + await hass.async_block_till_done() + + assert result["type"] is FlowResultType.ABORT + assert result["reason"] == "already_configured" async def test_form_invalid_auth( hass: HomeAssistant, mock_setup_entry: AsyncMock, mock_config_api: AsyncMock ) -> None: - """Test we get the form.""" + """Test config flow with a login error.""" result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_USER} ) From bc2afab31a3a766ea039870c10f5a89130092300 Mon Sep 17 00:00:00 2001 From: Jeff Stein Date: Tue, 10 Sep 2024 19:02:18 -0600 Subject: [PATCH 63/67] addressing pr stuff --- .../components/monarch_money/config_flow.py | 1 - .../monarch_money/test_config_flow.py | 3 +- .../components/weatherflow_cloud/conftest.py | 36 ------------------- 3 files changed, 1 insertion(+), 39 deletions(-) diff --git a/homeassistant/components/monarch_money/config_flow.py b/homeassistant/components/monarch_money/config_flow.py index 0cb077a4b56b54..410630c7cd8e36 100644 --- a/homeassistant/components/monarch_money/config_flow.py +++ b/homeassistant/components/monarch_money/config_flow.py @@ -93,7 +93,6 @@ async def validate_login( assert subs is not None subscription_id = subs.id return { - "title": "Monarch Money", CONF_TOKEN: monarch_client.token, CONF_ID: subscription_id, } diff --git a/tests/components/monarch_money/test_config_flow.py b/tests/components/monarch_money/test_config_flow.py index 08848f6d5fb552..b3e525ad76e6ad 100644 --- a/tests/components/monarch_money/test_config_flow.py +++ b/tests/components/monarch_money/test_config_flow.py @@ -29,7 +29,6 @@ async def test_form_simple( CONF_PASSWORD: "test-password", }, ) - await hass.async_block_till_done() assert result["type"] is FlowResultType.CREATE_ENTRY assert result["title"] == "Monarch Money" @@ -120,7 +119,7 @@ async def test_form_invalid_auth( async def test_form_mfa( hass: HomeAssistant, mock_setup_entry: AsyncMock, mock_config_api: AsyncMock ) -> None: - """Test we get the form.""" + """Test MFA enabled on account configuration.""" result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_USER} ) diff --git a/tests/components/weatherflow_cloud/conftest.py b/tests/components/weatherflow_cloud/conftest.py index d83ee082b262de..36b42bf24a83b5 100644 --- a/tests/components/weatherflow_cloud/conftest.py +++ b/tests/components/weatherflow_cloud/conftest.py @@ -113,39 +113,3 @@ def mock_api(): mock_api_class.return_value = mock_api yield mock_api - - -# -# @pytest.fixture -# def mock_api_with_lightning_error(): -# """Fixture for Mock WeatherFlowRestAPI.""" -# get_stations_response_data = StationsResponseREST.from_json( -# load_fixture("stations.json", DOMAIN) -# ) -# get_forecast_response_data = WeatherDataForecastREST.from_json( -# load_fixture("forecast.json", DOMAIN) -# ) -# get_observation_response_data = ObservationStationREST.from_json( -# load_fixture("station_observation_error.json", DOMAIN) -# ) -# -# data = { -# 24432: WeatherFlowDataREST( -# weather=get_forecast_response_data, -# observation=get_observation_response_data, -# station=get_stations_response_data.stations[0], -# device_observations=None, -# ) -# } -# -# with patch( -# "homeassistant.components.weatherflow_cloud.coordinator.WeatherFlowRestAPI", -# autospec=True, -# ) as mock_api_class: -# # Create an instance of AsyncMock for the API -# mock_api = AsyncMock() -# mock_api.get_all_data.return_value = data -# # Patch the class to return our mock_api instance -# mock_api_class.return_value = mock_api -# -# yield mock_api From 39536459f4ae5b0e927f21e917c4cb62e59e2e02 Mon Sep 17 00:00:00 2001 From: Jeff Stein Date: Tue, 10 Sep 2024 20:29:48 -0600 Subject: [PATCH 64/67] forgot snapshot --- .../monarch_money/snapshots/test_sensor.ambr | 1112 +++++++++++++++++ 1 file changed, 1112 insertions(+) create mode 100644 tests/components/monarch_money/snapshots/test_sensor.ambr diff --git a/tests/components/monarch_money/snapshots/test_sensor.ambr b/tests/components/monarch_money/snapshots/test_sensor.ambr new file mode 100644 index 00000000000000..cf7e0cb7b2fc75 --- /dev/null +++ b/tests/components/monarch_money/snapshots/test_sensor.ambr @@ -0,0 +1,1112 @@ +# serializer version: 1 +# name: test_all_entities[sensor.cashflow_expense_year_to_date-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.cashflow_expense_year_to_date', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Expense year to date', + 'platform': 'monarch_money', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'sum_expense', + 'unique_id': '222260252323873333_cashflow_sum_expense', + 'unit_of_measurement': '$', + }) +# --- +# name: test_all_entities[sensor.cashflow_expense_year_to_date-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'monetary', + 'friendly_name': 'Cashflow Expense year to date', + 'state_class': , + 'unit_of_measurement': '$', + }), + 'context': , + 'entity_id': 'sensor.cashflow_expense_year_to_date', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '-9000.0', + }) +# --- +# name: test_all_entities[sensor.cashflow_income_year_to_date-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.cashflow_income_year_to_date', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Income year to date', + 'platform': 'monarch_money', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'sum_income', + 'unique_id': '222260252323873333_cashflow_sum_income', + 'unit_of_measurement': '$', + }) +# --- +# name: test_all_entities[sensor.cashflow_income_year_to_date-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'monetary', + 'friendly_name': 'Cashflow Income year to date', + 'state_class': , + 'unit_of_measurement': '$', + }), + 'context': , + 'entity_id': 'sensor.cashflow_income_year_to_date', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '15000.0', + }) +# --- +# name: test_all_entities[sensor.cashflow_savings_rate-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.cashflow_savings_rate', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 1, + }), + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'Savings rate', + 'platform': 'monarch_money', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'savings_rate', + 'unique_id': '222260252323873333_cashflow_savings_rate', + 'unit_of_measurement': '%', + }) +# --- +# name: test_all_entities[sensor.cashflow_savings_rate-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Cashflow Savings rate', + 'unit_of_measurement': '%', + }), + 'context': , + 'entity_id': 'sensor.cashflow_savings_rate', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '40.0', + }) +# --- +# name: test_all_entities[sensor.cashflow_savings_year_to_date-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.cashflow_savings_year_to_date', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Savings year to date', + 'platform': 'monarch_money', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'savings', + 'unique_id': '222260252323873333_cashflow_savings', + 'unit_of_measurement': '$', + }) +# --- +# name: test_all_entities[sensor.cashflow_savings_year_to_date-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'monetary', + 'friendly_name': 'Cashflow Savings year to date', + 'state_class': , + 'unit_of_measurement': '$', + }), + 'context': , + 'entity_id': 'sensor.cashflow_savings_year_to_date', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '6000.0', + }) +# --- +# name: test_all_entities[sensor.manual_entry_wallet_balance-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.manual_entry_wallet_balance', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Balance', + 'platform': 'monarch_money', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'balance', + 'unique_id': '222260252323873333_186321412999033223_balance', + 'unit_of_measurement': '$', + }) +# --- +# name: test_all_entities[sensor.manual_entry_wallet_balance-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'attribution': 'Data provided by Monarch Money API via Manual entry', + 'device_class': 'monetary', + 'friendly_name': 'Manual entry Wallet Balance', + 'state_class': , + 'unit_of_measurement': '$', + }), + 'context': , + 'entity_id': 'sensor.manual_entry_wallet_balance', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '20.0', + }) +# --- +# name: test_all_entities[sensor.manual_entry_wallet_data_age-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': , + 'entity_id': 'sensor.manual_entry_wallet_data_age', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Data age', + 'platform': 'monarch_money', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'age', + 'unique_id': '222260252323873333_186321412999033223_age', + 'unit_of_measurement': None, + }) +# --- +# name: test_all_entities[sensor.manual_entry_wallet_data_age-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'attribution': 'Data provided by Monarch Money API via Manual entry', + 'device_class': 'timestamp', + 'friendly_name': 'Manual entry Wallet Data age', + }), + 'context': , + 'entity_id': 'sensor.manual_entry_wallet_data_age', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '2024-08-16T14:22:10+00:00', + }) +# --- +# name: test_all_entities[sensor.rando_bank_checking_balance-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.rando_bank_checking_balance', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Balance', + 'platform': 'monarch_money', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'balance', + 'unique_id': '222260252323873333_900000002_balance', + 'unit_of_measurement': '$', + }) +# --- +# name: test_all_entities[sensor.rando_bank_checking_balance-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'attribution': 'Data provided by Monarch Money API via PLAID', + 'device_class': 'monetary', + 'entity_picture': 'data:image/png;base64,base64Nonce', + 'friendly_name': 'Rando Bank Checking Balance', + 'state_class': , + 'unit_of_measurement': '$', + }), + 'context': , + 'entity_id': 'sensor.rando_bank_checking_balance', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '1000.02', + }) +# --- +# name: test_all_entities[sensor.rando_bank_checking_data_age-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': , + 'entity_id': 'sensor.rando_bank_checking_data_age', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Data age', + 'platform': 'monarch_money', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'age', + 'unique_id': '222260252323873333_900000002_age', + 'unit_of_measurement': None, + }) +# --- +# name: test_all_entities[sensor.rando_bank_checking_data_age-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'attribution': 'Data provided by Monarch Money API via PLAID', + 'device_class': 'timestamp', + 'friendly_name': 'Rando Bank Checking Data age', + }), + 'context': , + 'entity_id': 'sensor.rando_bank_checking_data_age', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '2024-02-17T11:21:05+00:00', + }) +# --- +# name: test_all_entities[sensor.rando_brokerage_brokerage_balance-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.rando_brokerage_brokerage_balance', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Balance', + 'platform': 'monarch_money', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'balance', + 'unique_id': '222260252323873333_900000000_balance', + 'unit_of_measurement': '$', + }) +# --- +# name: test_all_entities[sensor.rando_brokerage_brokerage_balance-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'attribution': 'Data provided by Monarch Money API via PLAID', + 'device_class': 'monetary', + 'entity_picture': 'base64Nonce', + 'friendly_name': 'Rando Brokerage Brokerage Balance', + 'state_class': , + 'unit_of_measurement': '$', + }), + 'context': , + 'entity_id': 'sensor.rando_brokerage_brokerage_balance', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '1000.5', + }) +# --- +# name: test_all_entities[sensor.rando_brokerage_brokerage_data_age-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': , + 'entity_id': 'sensor.rando_brokerage_brokerage_data_age', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Data age', + 'platform': 'monarch_money', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'age', + 'unique_id': '222260252323873333_900000000_age', + 'unit_of_measurement': None, + }) +# --- +# name: test_all_entities[sensor.rando_brokerage_brokerage_data_age-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'attribution': 'Data provided by Monarch Money API via PLAID', + 'device_class': 'timestamp', + 'friendly_name': 'Rando Brokerage Brokerage Data age', + }), + 'context': , + 'entity_id': 'sensor.rando_brokerage_brokerage_data_age', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '2022-05-26T00:56:41+00:00', + }) +# --- +# name: test_all_entities[sensor.rando_credit_credit_card_balance-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.rando_credit_credit_card_balance', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Balance', + 'platform': 'monarch_money', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'balance', + 'unique_id': '222260252323873333_9000000007_balance', + 'unit_of_measurement': '$', + }) +# --- +# name: test_all_entities[sensor.rando_credit_credit_card_balance-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'attribution': 'Data provided by Monarch Money API via FINICITY', + 'device_class': 'monetary', + 'entity_picture': 'data:image/png;base64,base64Nonce', + 'friendly_name': 'Rando Credit Credit Card Balance', + 'state_class': , + 'unit_of_measurement': '$', + }), + 'context': , + 'entity_id': 'sensor.rando_credit_credit_card_balance', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '-200.0', + }) +# --- +# name: test_all_entities[sensor.rando_credit_credit_card_data_age-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': , + 'entity_id': 'sensor.rando_credit_credit_card_data_age', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Data age', + 'platform': 'monarch_money', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'age', + 'unique_id': '222260252323873333_9000000007_age', + 'unit_of_measurement': None, + }) +# --- +# name: test_all_entities[sensor.rando_credit_credit_card_data_age-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'attribution': 'Data provided by Monarch Money API via FINICITY', + 'device_class': 'timestamp', + 'friendly_name': 'Rando Credit Credit Card Data age', + }), + 'context': , + 'entity_id': 'sensor.rando_credit_credit_card_data_age', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '2022-12-10T18:17:06+00:00', + }) +# --- +# name: test_all_entities[sensor.rando_employer_investments_401_k_balance-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.rando_employer_investments_401_k_balance', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Balance', + 'platform': 'monarch_money', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'balance', + 'unique_id': '222260252323873333_90000000022_balance', + 'unit_of_measurement': '$', + }) +# --- +# name: test_all_entities[sensor.rando_employer_investments_401_k_balance-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'attribution': 'Data provided by Monarch Money API via FINICITY', + 'device_class': 'monetary', + 'entity_picture': 'data:image/png;base64,base64Nonce', + 'friendly_name': 'Rando Employer Investments 401.k Balance', + 'state_class': , + 'unit_of_measurement': '$', + }), + 'context': , + 'entity_id': 'sensor.rando_employer_investments_401_k_balance', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '100000.35', + }) +# --- +# name: test_all_entities[sensor.rando_employer_investments_401_k_data_age-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': , + 'entity_id': 'sensor.rando_employer_investments_401_k_data_age', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Data age', + 'platform': 'monarch_money', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'age', + 'unique_id': '222260252323873333_90000000022_age', + 'unit_of_measurement': None, + }) +# --- +# name: test_all_entities[sensor.rando_employer_investments_401_k_data_age-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'attribution': 'Data provided by Monarch Money API via FINICITY', + 'device_class': 'timestamp', + 'friendly_name': 'Rando Employer Investments 401.k Data age', + }), + 'context': , + 'entity_id': 'sensor.rando_employer_investments_401_k_data_age', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '2024-02-17T08:13:10+00:00', + }) +# --- +# name: test_all_entities[sensor.rando_investments_roth_ira_balance-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.rando_investments_roth_ira_balance', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Balance', + 'platform': 'monarch_money', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'balance', + 'unique_id': '222260252323873333_900000000012_balance', + 'unit_of_measurement': '$', + }) +# --- +# name: test_all_entities[sensor.rando_investments_roth_ira_balance-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'attribution': 'Data provided by Monarch Money API via PLAID', + 'device_class': 'monetary', + 'entity_picture': 'data:image/png;base64,base64Nonce', + 'friendly_name': 'Rando Investments Roth IRA Balance', + 'state_class': , + 'unit_of_measurement': '$', + }), + 'context': , + 'entity_id': 'sensor.rando_investments_roth_ira_balance', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '10000.43', + }) +# --- +# name: test_all_entities[sensor.rando_investments_roth_ira_data_age-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': , + 'entity_id': 'sensor.rando_investments_roth_ira_data_age', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Data age', + 'platform': 'monarch_money', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'age', + 'unique_id': '222260252323873333_900000000012_age', + 'unit_of_measurement': None, + }) +# --- +# name: test_all_entities[sensor.rando_investments_roth_ira_data_age-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'attribution': 'Data provided by Monarch Money API via PLAID', + 'device_class': 'timestamp', + 'friendly_name': 'Rando Investments Roth IRA Data age', + }), + 'context': , + 'entity_id': 'sensor.rando_investments_roth_ira_data_age', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '2024-02-17T13:32:21+00:00', + }) +# --- +# name: test_all_entities[sensor.rando_mortgage_mortgage_balance-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.rando_mortgage_mortgage_balance', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Balance', + 'platform': 'monarch_money', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'balance', + 'unique_id': '222260252323873333_90000000030_balance', + 'unit_of_measurement': '$', + }) +# --- +# name: test_all_entities[sensor.rando_mortgage_mortgage_balance-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'attribution': 'Data provided by Monarch Money API via PLAID', + 'device_class': 'monetary', + 'entity_picture': 'data:image/png;base64,base64Nonce', + 'friendly_name': 'Rando Mortgage Mortgage Balance', + 'state_class': , + 'unit_of_measurement': '$', + }), + 'context': , + 'entity_id': 'sensor.rando_mortgage_mortgage_balance', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '0.0', + }) +# --- +# name: test_all_entities[sensor.rando_mortgage_mortgage_data_age-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': , + 'entity_id': 'sensor.rando_mortgage_mortgage_data_age', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Data age', + 'platform': 'monarch_money', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'age', + 'unique_id': '222260252323873333_90000000030_age', + 'unit_of_measurement': None, + }) +# --- +# name: test_all_entities[sensor.rando_mortgage_mortgage_data_age-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'attribution': 'Data provided by Monarch Money API via PLAID', + 'device_class': 'timestamp', + 'friendly_name': 'Rando Mortgage Mortgage Data age', + }), + 'context': , + 'entity_id': 'sensor.rando_mortgage_mortgage_data_age', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '2023-08-16T01:41:36+00:00', + }) +# --- +# name: test_all_entities[sensor.vinaudit_2050_toyota_rav8_data_age-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': , + 'entity_id': 'sensor.vinaudit_2050_toyota_rav8_data_age', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Data age', + 'platform': 'monarch_money', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'age', + 'unique_id': '222260252323873333_121212192626186051_age', + 'unit_of_measurement': None, + }) +# --- +# name: test_all_entities[sensor.vinaudit_2050_toyota_rav8_data_age-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'attribution': 'Data provided by Monarch Money API via Manual entry', + 'device_class': 'timestamp', + 'friendly_name': 'VinAudit 2050 Toyota RAV8 Data age', + }), + 'context': , + 'entity_id': 'sensor.vinaudit_2050_toyota_rav8_data_age', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '2024-08-16T17:37:21+00:00', + }) +# --- +# name: test_all_entities[sensor.vinaudit_2050_toyota_rav8_value-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.vinaudit_2050_toyota_rav8_value', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Value', + 'platform': 'monarch_money', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'value', + 'unique_id': '222260252323873333_121212192626186051_value', + 'unit_of_measurement': '$', + }) +# --- +# name: test_all_entities[sensor.vinaudit_2050_toyota_rav8_value-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'attribution': 'Data provided by Monarch Money API via Manual entry', + 'device_class': 'monetary', + 'entity_picture': 'https://api.monarchmoney.com/cdn-cgi/image/width=128/images/institution/159427559853802644', + 'friendly_name': 'VinAudit 2050 Toyota RAV8 Value', + 'state_class': , + 'unit_of_measurement': '$', + }), + 'context': , + 'entity_id': 'sensor.vinaudit_2050_toyota_rav8_value', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '11075.58', + }) +# --- +# name: test_all_entities[sensor.zillow_house_balance-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.zillow_house_balance', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Balance', + 'platform': 'monarch_money', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'balance', + 'unique_id': '222260252323873333_90000000020_balance', + 'unit_of_measurement': '$', + }) +# --- +# name: test_all_entities[sensor.zillow_house_balance-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'attribution': 'Data provided by Monarch Money API via Manual entry', + 'device_class': 'monetary', + 'entity_picture': 'data:image/png;base64,base64Nonce', + 'friendly_name': 'Zillow House Balance', + 'state_class': , + 'unit_of_measurement': '$', + }), + 'context': , + 'entity_id': 'sensor.zillow_house_balance', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '123000.0', + }) +# --- +# name: test_all_entities[sensor.zillow_house_data_age-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': , + 'entity_id': 'sensor.zillow_house_data_age', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Data age', + 'platform': 'monarch_money', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'age', + 'unique_id': '222260252323873333_90000000020_age', + 'unit_of_measurement': None, + }) +# --- +# name: test_all_entities[sensor.zillow_house_data_age-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'attribution': 'Data provided by Monarch Money API via Manual entry', + 'device_class': 'timestamp', + 'friendly_name': 'Zillow House Data age', + }), + 'context': , + 'entity_id': 'sensor.zillow_house_data_age', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '2024-02-12T09:00:25+00:00', + }) +# --- From 1bb43748d14bac3980929904be8a92ef28444ed4 Mon Sep 17 00:00:00 2001 From: Joostlek Date: Wed, 11 Sep 2024 16:02:46 +0200 Subject: [PATCH 65/67] Fix --- .../monarch_money/test_config_flow.py | 32 +++++++------------ 1 file changed, 11 insertions(+), 21 deletions(-) diff --git a/tests/components/monarch_money/test_config_flow.py b/tests/components/monarch_money/test_config_flow.py index b3e525ad76e6ad..03f0df0c526149 100644 --- a/tests/components/monarch_money/test_config_flow.py +++ b/tests/components/monarch_money/test_config_flow.py @@ -4,7 +4,6 @@ from monarchmoney import LoginFailedException, RequireMFAException -from homeassistant import config_entries from homeassistant.components.monarch_money.const import CONF_MFA_CODE, DOMAIN from homeassistant.config_entries import SOURCE_USER from homeassistant.const import CONF_EMAIL, CONF_PASSWORD, CONF_TOKEN @@ -35,7 +34,7 @@ async def test_form_simple( assert result["data"] == { CONF_TOKEN: "mocked_token", } - assert result["context"]["unique_id"] == "222260252323873333" + assert result["result"].unique_id == "222260252323873333" assert len(mock_setup_entry.mock_calls) == 1 @@ -49,11 +48,8 @@ async def test_add_duplicate_entry( mock_config_entry.add_to_hass(hass) result = await hass.config_entries.flow.async_init( - DOMAIN, context={"source": config_entries.SOURCE_USER} + DOMAIN, context={"source": SOURCE_USER} ) - assert result["type"] == FlowResultType.FORM - assert result["errors"] == {} - assert result["type"] is FlowResultType.FORM assert result["errors"] == {} @@ -64,7 +60,6 @@ async def test_add_duplicate_entry( CONF_PASSWORD: "test-password", }, ) - await hass.async_block_till_done() assert result["type"] is FlowResultType.ABORT assert result["reason"] == "already_configured" @@ -75,9 +70,9 @@ async def test_form_invalid_auth( ) -> None: """Test config flow with a login error.""" result = await hass.config_entries.flow.async_init( - DOMAIN, context={"source": config_entries.SOURCE_USER} + DOMAIN, context={"source": SOURCE_USER} ) - assert result["type"] == FlowResultType.FORM + assert result["type"] is FlowResultType.FORM assert result["errors"] == {} # Change the login mock to raise an MFA required error @@ -92,9 +87,8 @@ async def test_form_invalid_auth( CONF_PASSWORD: "test-password", }, ) - await hass.async_block_till_done() - assert result["type"] == FlowResultType.FORM + assert result["type"] is FlowResultType.FORM assert result["errors"] == {"base": "invalid_auth"} mock_config_api.return_value.login.side_effect = None @@ -105,9 +99,8 @@ async def test_form_invalid_auth( CONF_PASSWORD: "test-password", }, ) - await hass.async_block_till_done() - assert result["type"] == FlowResultType.CREATE_ENTRY + assert result["type"] is FlowResultType.CREATE_ENTRY assert result["title"] == "Monarch Money" assert result["data"] == { CONF_TOKEN: "mocked_token", @@ -121,9 +114,9 @@ async def test_form_mfa( ) -> None: """Test MFA enabled on account configuration.""" result = await hass.config_entries.flow.async_init( - DOMAIN, context={"source": config_entries.SOURCE_USER} + DOMAIN, context={"source": SOURCE_USER} ) - assert result["type"] == FlowResultType.FORM + assert result["type"] is FlowResultType.FORM assert result["errors"] == {} # Change the login mock to raise an MFA required error @@ -136,9 +129,8 @@ async def test_form_mfa( CONF_PASSWORD: "test-password", }, ) - await hass.async_block_till_done() - assert result["type"] == FlowResultType.FORM + assert result["type"] is FlowResultType.FORM assert result["errors"] == {"base": "mfa_required"} assert result["step_id"] == "user" @@ -150,9 +142,8 @@ async def test_form_mfa( CONF_MFA_CODE: "123456", }, ) - await hass.async_block_till_done() - assert result["type"] == FlowResultType.FORM + assert result["type"] is FlowResultType.FORM assert result["errors"] == {"base": "bad_mfa"} assert result["step_id"] == "user" @@ -164,9 +155,8 @@ async def test_form_mfa( CONF_MFA_CODE: "123456", }, ) - await hass.async_block_till_done() - assert result["type"] == FlowResultType.CREATE_ENTRY + assert result["type"] is FlowResultType.CREATE_ENTRY assert result["title"] == "Monarch Money" assert result["data"] == { CONF_TOKEN: "mocked_token", From ba63f8167e4247027250d47b30c0b2d0be41fd66 Mon Sep 17 00:00:00 2001 From: Joostlek Date: Wed, 11 Sep 2024 16:05:30 +0200 Subject: [PATCH 66/67] Fix --- .../components/weatherflow_cloud/conftest.py | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/tests/components/weatherflow_cloud/conftest.py b/tests/components/weatherflow_cloud/conftest.py index 36b42bf24a83b5..d83ee082b262de 100644 --- a/tests/components/weatherflow_cloud/conftest.py +++ b/tests/components/weatherflow_cloud/conftest.py @@ -113,3 +113,39 @@ def mock_api(): mock_api_class.return_value = mock_api yield mock_api + + +# +# @pytest.fixture +# def mock_api_with_lightning_error(): +# """Fixture for Mock WeatherFlowRestAPI.""" +# get_stations_response_data = StationsResponseREST.from_json( +# load_fixture("stations.json", DOMAIN) +# ) +# get_forecast_response_data = WeatherDataForecastREST.from_json( +# load_fixture("forecast.json", DOMAIN) +# ) +# get_observation_response_data = ObservationStationREST.from_json( +# load_fixture("station_observation_error.json", DOMAIN) +# ) +# +# data = { +# 24432: WeatherFlowDataREST( +# weather=get_forecast_response_data, +# observation=get_observation_response_data, +# station=get_stations_response_data.stations[0], +# device_observations=None, +# ) +# } +# +# with patch( +# "homeassistant.components.weatherflow_cloud.coordinator.WeatherFlowRestAPI", +# autospec=True, +# ) as mock_api_class: +# # Create an instance of AsyncMock for the API +# mock_api = AsyncMock() +# mock_api.get_all_data.return_value = data +# # Patch the class to return our mock_api instance +# mock_api_class.return_value = mock_api +# +# yield mock_api From 0727754a794e94a7546a465703ae1e5bbcfd0446 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Wed, 11 Sep 2024 16:05:59 +0200 Subject: [PATCH 67/67] Update homeassistant/components/monarch_money/sensor.py --- homeassistant/components/monarch_money/sensor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/monarch_money/sensor.py b/homeassistant/components/monarch_money/sensor.py index d17b86b0e1d64a..fe7c728cf4194a 100644 --- a/homeassistant/components/monarch_money/sensor.py +++ b/homeassistant/components/monarch_money/sensor.py @@ -170,7 +170,7 @@ class MonarchMoneySensor(MonarchMoneyAccountEntity, SensorEntity): entity_description: MonarchMoneyAccountSensorEntityDescription @property - def native_value(self) -> StateType | datetime | None: + def native_value(self) -> StateType | datetime: """Return the state.""" return self.entity_description.value_fn(self.account_data)