Bump Intellifire to 4.1.9 (#121091)

* rebase

* Minor patch to fix duplicate DeviceInfo beign created - if data hasnt updated yet

* rebase

* Minor patch to fix duplicate DeviceInfo beign created - if data hasnt updated yet

* fixing formatting

* Update homeassistant/components/intellifire/__init__.py

Co-authored-by: Erik Montnemery <erik@montnemery.com>

* Update homeassistant/components/intellifire/__init__.py

Co-authored-by: Erik Montnemery <erik@montnemery.com>

* Removing cloud connectivity sensor - leaving local one in

* Renaming class to something more useful

* addressing pr

* Update homeassistant/components/intellifire/__init__.py

Co-authored-by: Erik Montnemery <erik@montnemery.com>

* add ruff exception

* Fix test annotations

* remove access to private variable

* Bumping to 4.1.9 instead of 4.1.5

* A renaming

* rename

* Updated testing

* Update __init__.py

Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>

* updateing styrings

* Update tests/components/intellifire/conftest.py

Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>

* Testing refactor - WIP

* everything is passing - cleanup still needed

* cleaning up comments

* update pr

* unrename

* Update homeassistant/components/intellifire/coordinator.py

Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>

* fixing sentence

* fixed fixture and removed error codes

* reverted a bad change

* fixing strings.json

* revert renaming

* fix

* typing inother pr

* adding extra tests - one has a really dumb name

* using a real value

* added a migration in

* Update homeassistant/components/intellifire/config_flow.py

Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>

* Update tests/components/intellifire/test_init.py

Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>

* cleanup continues

* addressing pr

* switch back to debug

* Update tests/components/intellifire/conftest.py

Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>

* some changes

* restore property mock cuase didnt work otherwise

* cleanup has begun

* removed extra text

* addressing pr stuff

* fixed reauth

---------

Co-authored-by: Erik Montnemery <erik@montnemery.com>
Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
pull/124880/head
Jeef 2024-09-01 04:48:38 -06:00 committed by Bram Kragten
parent 411b014da2
commit 234f32265e
28 changed files with 2445 additions and 678 deletions

View File

@ -2,15 +2,17 @@
from __future__ import annotations
from aiohttp import ClientConnectionError
from intellifire4py import IntellifireControlAsync
from intellifire4py.exceptions import LoginException
from intellifire4py.intellifire import IntellifireAPICloud, IntellifireAPILocal
import asyncio
from intellifire4py import UnifiedFireplace
from intellifire4py.cloud_interface import IntelliFireCloudInterface
from intellifire4py.model import IntelliFireCommonFireplaceData
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import (
CONF_API_KEY,
CONF_HOST,
CONF_IP_ADDRESS,
CONF_PASSWORD,
CONF_USERNAME,
Platform,
@ -18,7 +20,18 @@ from homeassistant.const import (
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady
from .const import CONF_USER_ID, DOMAIN, LOGGER
from .const import (
CONF_AUTH_COOKIE,
CONF_CONTROL_MODE,
CONF_READ_MODE,
CONF_SERIAL,
CONF_USER_ID,
CONF_WEB_CLIENT_ID,
DOMAIN,
INIT_WAIT_TIME_SECONDS,
LOGGER,
STARTUP_TIMEOUT,
)
from .coordinator import IntellifireDataUpdateCoordinator
PLATFORMS = [
@ -32,79 +45,114 @@ PLATFORMS = [
]
def _construct_common_data(entry: ConfigEntry) -> IntelliFireCommonFireplaceData:
"""Convert config entry data into IntelliFireCommonFireplaceData."""
return IntelliFireCommonFireplaceData(
auth_cookie=entry.data[CONF_AUTH_COOKIE],
user_id=entry.data[CONF_USER_ID],
web_client_id=entry.data[CONF_WEB_CLIENT_ID],
serial=entry.data[CONF_SERIAL],
api_key=entry.data[CONF_API_KEY],
ip_address=entry.data[CONF_IP_ADDRESS],
read_mode=entry.options[CONF_READ_MODE],
control_mode=entry.options[CONF_CONTROL_MODE],
)
async def async_migrate_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool:
"""Migrate entries."""
LOGGER.debug(
"Migrating configuration from version %s.%s",
config_entry.version,
config_entry.minor_version,
)
if config_entry.version == 1:
new = {**config_entry.data}
if config_entry.minor_version < 2:
username = config_entry.data[CONF_USERNAME]
password = config_entry.data[CONF_PASSWORD]
# Create a Cloud Interface
async with IntelliFireCloudInterface() as cloud_interface:
await cloud_interface.login_with_credentials(
username=username, password=password
)
new_data = cloud_interface.user_data.get_data_for_ip(new[CONF_HOST])
if not new_data:
raise ConfigEntryAuthFailed
new[CONF_API_KEY] = new_data.api_key
new[CONF_WEB_CLIENT_ID] = new_data.web_client_id
new[CONF_AUTH_COOKIE] = new_data.auth_cookie
new[CONF_IP_ADDRESS] = new_data.ip_address
new[CONF_SERIAL] = new_data.serial
hass.config_entries.async_update_entry(
config_entry,
data=new,
options={CONF_READ_MODE: "local", CONF_CONTROL_MODE: "local"},
unique_id=new[CONF_SERIAL],
version=1,
minor_version=2,
)
LOGGER.debug("Pseudo Migration %s successful", config_entry.version)
return True
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Set up IntelliFire from a config entry."""
LOGGER.debug("Setting up config entry: %s", entry.unique_id)
if CONF_USERNAME not in entry.data:
LOGGER.debug("Old config entry format detected: %s", entry.unique_id)
LOGGER.debug("Config entry without username detected: %s", entry.unique_id)
raise ConfigEntryAuthFailed
ift_control = IntellifireControlAsync(
fireplace_ip=entry.data[CONF_HOST],
)
try:
await ift_control.login(
username=entry.data[CONF_USERNAME],
password=entry.data[CONF_PASSWORD],
fireplace: UnifiedFireplace = (
await UnifiedFireplace.build_fireplace_from_common(
_construct_common_data(entry)
)
)
except (ConnectionError, ClientConnectionError) as err:
raise ConfigEntryNotReady from err
except LoginException as err:
raise ConfigEntryAuthFailed(err) from err
finally:
await ift_control.close()
# Extract API Key and User_ID from ift_control
# Eventually this will migrate to using IntellifireAPICloud
if CONF_USER_ID not in entry.data or CONF_API_KEY not in entry.data:
LOGGER.info(
"Updating intellifire config entry for %s with api information",
entry.unique_id,
)
cloud_api = IntellifireAPICloud()
await cloud_api.login(
username=entry.data[CONF_USERNAME],
password=entry.data[CONF_PASSWORD],
)
api_key = cloud_api.get_fireplace_api_key()
user_id = cloud_api.get_user_id()
# Update data entry
hass.config_entries.async_update_entry(
entry,
data={
**entry.data,
CONF_API_KEY: api_key,
CONF_USER_ID: user_id,
},
LOGGER.debug("Waiting for Fireplace to Initialize")
await asyncio.wait_for(
_async_wait_for_initialization(fireplace), timeout=STARTUP_TIMEOUT
)
except TimeoutError as err:
raise ConfigEntryNotReady(
"Initialization of fireplace timed out after 10 minutes"
) from err
else:
api_key = entry.data[CONF_API_KEY]
user_id = entry.data[CONF_USER_ID]
# Instantiate local control
api = IntellifireAPILocal(
fireplace_ip=entry.data[CONF_HOST],
api_key=api_key,
user_id=user_id,
# Construct coordinator
data_update_coordinator = IntellifireDataUpdateCoordinator(
hass=hass, fireplace=fireplace
)
# Define the update coordinator
coordinator = IntellifireDataUpdateCoordinator(
hass=hass,
api=api,
)
LOGGER.debug("Fireplace to Initialized - Awaiting first refresh")
await data_update_coordinator.async_config_entry_first_refresh()
hass.data.setdefault(DOMAIN, {})[entry.entry_id] = data_update_coordinator
await coordinator.async_config_entry_first_refresh()
hass.data.setdefault(DOMAIN, {})[entry.entry_id] = coordinator
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
return True
async def _async_wait_for_initialization(
fireplace: UnifiedFireplace, timeout=STARTUP_TIMEOUT
):
"""Wait for a fireplace to be initialized."""
while (
fireplace.data.ipv4_address == "127.0.0.1" and fireplace.data.serial == "unset"
):
LOGGER.debug(f"Waiting for fireplace to initialize [{fireplace.read_mode}]")
await asyncio.sleep(INIT_WAIT_TIME_SECONDS)
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Unload a config entry."""
if unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS):

View File

@ -5,7 +5,7 @@ from __future__ import annotations
from collections.abc import Callable
from dataclasses import dataclass
from intellifire4py import IntellifirePollData
from intellifire4py.model import IntelliFirePollData
from homeassistant.components.binary_sensor import (
BinarySensorDeviceClass,
@ -26,7 +26,7 @@ from .entity import IntellifireEntity
class IntellifireBinarySensorRequiredKeysMixin:
"""Mixin for required keys."""
value_fn: Callable[[IntellifirePollData], bool]
value_fn: Callable[[IntelliFirePollData], bool]
@dataclass(frozen=True)

View File

@ -69,7 +69,7 @@ class IntellifireClimate(IntellifireEntity, ClimateEntity):
super().__init__(coordinator, description)
if coordinator.data.thermostat_on:
self.last_temp = coordinator.data.thermostat_setpoint_c
self.last_temp = int(coordinator.data.thermostat_setpoint_c)
@property
def hvac_mode(self) -> HVACMode:

View File

@ -7,16 +7,33 @@ from dataclasses import dataclass
from typing import Any
from aiohttp import ClientConnectionError
from intellifire4py import AsyncUDPFireplaceFinder
from intellifire4py.exceptions import LoginException
from intellifire4py.intellifire import IntellifireAPICloud, IntellifireAPILocal
from intellifire4py.cloud_interface import IntelliFireCloudInterface
from intellifire4py.exceptions import LoginError
from intellifire4py.local_api import IntelliFireAPILocal
from intellifire4py.model import IntelliFireCommonFireplaceData
import voluptuous as vol
from homeassistant.components.dhcp import DhcpServiceInfo
from homeassistant.config_entries import ConfigFlow, ConfigFlowResult
from homeassistant.const import CONF_API_KEY, CONF_HOST, CONF_PASSWORD, CONF_USERNAME
from homeassistant.config_entries import ConfigEntry, ConfigFlow, ConfigFlowResult
from homeassistant.const import (
CONF_API_KEY,
CONF_HOST,
CONF_IP_ADDRESS,
CONF_PASSWORD,
CONF_USERNAME,
)
from .const import CONF_USER_ID, DOMAIN, LOGGER
from .const import (
API_MODE_LOCAL,
CONF_AUTH_COOKIE,
CONF_CONTROL_MODE,
CONF_READ_MODE,
CONF_SERIAL,
CONF_USER_ID,
CONF_WEB_CLIENT_ID,
DOMAIN,
LOGGER,
)
STEP_USER_DATA_SCHEMA = vol.Schema({vol.Required(CONF_HOST): str})
@ -31,17 +48,20 @@ class DiscoveredHostInfo:
serial: str | None
async def validate_host_input(host: str, dhcp_mode: bool = False) -> str:
async def _async_poll_local_fireplace_for_serial(
host: str, dhcp_mode: bool = False
) -> str:
"""Validate the user input allows us to connect.
Data has the keys from STEP_USER_DATA_SCHEMA with values provided by the user.
"""
LOGGER.debug("Instantiating IntellifireAPI with host: [%s]", host)
api = IntellifireAPILocal(fireplace_ip=host)
api = IntelliFireAPILocal(fireplace_ip=host)
await api.poll(suppress_warnings=dhcp_mode)
serial = api.data.serial
LOGGER.debug("Found a fireplace: %s", serial)
# Return the serial number which will be used to calculate a unique ID for the device/sensors
return serial
@ -50,239 +70,206 @@ class IntelliFireConfigFlow(ConfigFlow, domain=DOMAIN):
"""Handle a config flow for IntelliFire."""
VERSION = 1
MINOR_VERSION = 2
def __init__(self) -> None:
"""Initialize the Config Flow Handler."""
self._host: str = ""
self._serial: str = ""
self._not_configured_hosts: list[DiscoveredHostInfo] = []
# DHCP Variables
self._dhcp_discovered_serial: str = "" # used only in discovery mode
self._discovered_host: DiscoveredHostInfo
self._dhcp_mode = False
self._is_reauth = False
self._not_configured_hosts: list[DiscoveredHostInfo] = []
self._reauth_needed: DiscoveredHostInfo
async def _find_fireplaces(self):
"""Perform UDP discovery."""
fireplace_finder = AsyncUDPFireplaceFinder()
discovered_hosts = await fireplace_finder.search_fireplace(timeout=12)
configured_hosts = {
entry.data[CONF_HOST]
for entry in self._async_current_entries(include_ignore=False)
if CONF_HOST in entry.data # CONF_HOST will be missing for ignored entries
}
self._configured_serials: list[str] = []
self._not_configured_hosts = [
DiscoveredHostInfo(ip, None)
for ip in discovered_hosts
if ip not in configured_hosts
]
LOGGER.debug("Discovered Hosts: %s", discovered_hosts)
LOGGER.debug("Configured Hosts: %s", configured_hosts)
LOGGER.debug("Not Configured Hosts: %s", self._not_configured_hosts)
async def validate_api_access_and_create_or_update(
self, *, host: str, username: str, password: str, serial: str
):
"""Validate username/password against api."""
LOGGER.debug("Attempting login to iftapi with: %s", username)
ift_cloud = IntellifireAPICloud()
await ift_cloud.login(username=username, password=password)
api_key = ift_cloud.get_fireplace_api_key()
user_id = ift_cloud.get_user_id()
data = {
CONF_HOST: host,
CONF_PASSWORD: password,
CONF_USERNAME: username,
CONF_API_KEY: api_key,
CONF_USER_ID: user_id,
}
# Update or Create
existing_entry = await self.async_set_unique_id(serial)
if existing_entry:
self.hass.config_entries.async_update_entry(existing_entry, data=data)
await self.hass.config_entries.async_reload(existing_entry.entry_id)
return self.async_abort(reason="reauth_successful")
return self.async_create_entry(title=f"Fireplace {serial}", data=data)
async def async_step_api_config(
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
"""Configure API access."""
errors = {}
control_schema = vol.Schema(
{
vol.Required(CONF_USERNAME): str,
vol.Required(CONF_PASSWORD): str,
}
)
if user_input is not None:
control_schema = vol.Schema(
{
vol.Required(
CONF_USERNAME, default=user_input.get(CONF_USERNAME, "")
): str,
vol.Required(
CONF_PASSWORD, default=user_input.get(CONF_PASSWORD, "")
): str,
}
)
try:
return await self.validate_api_access_and_create_or_update(
host=self._host,
username=user_input[CONF_USERNAME],
password=user_input[CONF_PASSWORD],
serial=self._serial,
)
except (ConnectionError, ClientConnectionError):
errors["base"] = "iftapi_connect"
LOGGER.error(
"Could not connect to iftapi.net over https - verify connectivity"
)
except LoginException:
errors["base"] = "api_error"
LOGGER.error("Invalid credentials for iftapi.net")
return self.async_show_form(
step_id="api_config", errors=errors, data_schema=control_schema
)
async def _async_validate_ip_and_continue(self, host: str) -> ConfigFlowResult:
"""Validate local config and continue."""
self._async_abort_entries_match({CONF_HOST: host})
self._serial = await validate_host_input(host)
await self.async_set_unique_id(self._serial, raise_on_progress=False)
self._abort_if_unique_id_configured(updates={CONF_HOST: host})
# Store current data and jump to next stage
self._host = host
return await self.async_step_api_config()
async def async_step_manual_device_entry(self, user_input=None):
"""Handle manual input of local IP configuration."""
LOGGER.debug("STEP: manual_device_entry")
errors = {}
self._host = user_input.get(CONF_HOST) if user_input else None
if user_input is not None:
try:
return await self._async_validate_ip_and_continue(self._host)
except (ConnectionError, ClientConnectionError):
errors["base"] = "cannot_connect"
return self.async_show_form(
step_id="manual_device_entry",
errors=errors,
data_schema=vol.Schema({vol.Required(CONF_HOST, default=self._host): str}),
)
async def async_step_pick_device(
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
"""Pick which device to configure."""
errors = {}
LOGGER.debug("STEP: pick_device")
if user_input is not None:
if user_input[CONF_HOST] == MANUAL_ENTRY_STRING:
return await self.async_step_manual_device_entry()
try:
return await self._async_validate_ip_and_continue(user_input[CONF_HOST])
except (ConnectionError, ClientConnectionError):
errors["base"] = "cannot_connect"
return self.async_show_form(
step_id="pick_device",
errors=errors,
data_schema=vol.Schema(
{
vol.Required(CONF_HOST): vol.In(
[host.ip for host in self._not_configured_hosts]
+ [MANUAL_ENTRY_STRING]
)
}
),
)
# Define a cloud api interface we can use
self.cloud_api_interface = IntelliFireCloudInterface()
async def async_step_user(
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
"""Start the user flow."""
# Launch fireplaces discovery
await self._find_fireplaces()
LOGGER.debug("STEP: user")
if self._not_configured_hosts:
LOGGER.debug("Running Step: pick_device")
return await self.async_step_pick_device()
LOGGER.debug("Running Step: manual_device_entry")
return await self.async_step_manual_device_entry()
current_entries = self._async_current_entries(include_ignore=False)
self._configured_serials = [
entry.data[CONF_SERIAL] for entry in current_entries
]
return await self.async_step_cloud_api()
async def async_step_cloud_api(
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
"""Authenticate against IFTAPI Cloud in order to see configured devices.
Local control of IntelliFire devices requires that the user download the correct API KEY which is only available on the cloud. Cloud control of the devices requires the user has at least once authenticated against the cloud and a set of cookie variables have been stored locally.
"""
errors: dict[str, str] = {}
LOGGER.debug("STEP: cloud_api")
if user_input is not None:
try:
async with self.cloud_api_interface as cloud_interface:
await cloud_interface.login_with_credentials(
username=user_input[CONF_USERNAME],
password=user_input[CONF_PASSWORD],
)
# If login was successful pass username/password to next step
return await self.async_step_pick_cloud_device()
except LoginError:
errors["base"] = "api_error"
return self.async_show_form(
step_id="cloud_api",
errors=errors,
data_schema=vol.Schema(
{
vol.Required(CONF_USERNAME): str,
vol.Required(CONF_PASSWORD): str,
}
),
)
async def async_step_pick_cloud_device(
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
"""Step to select a device from the cloud.
We can only get here if we have logged in. If there is only one device available it will be auto-configured,
else the user will be given a choice to pick a device.
"""
errors: dict[str, str] = {}
LOGGER.debug(
f"STEP: pick_cloud_device: {user_input} - DHCP_MODE[{self._dhcp_mode}"
)
if self._dhcp_mode or user_input is not None:
if self._dhcp_mode:
serial = self._dhcp_discovered_serial
LOGGER.debug(f"DHCP Mode detected for serial [{serial}]")
if user_input is not None:
serial = user_input[CONF_SERIAL]
# Run a unique ID Check prior to anything else
await self.async_set_unique_id(serial)
self._abort_if_unique_id_configured(updates={CONF_SERIAL: serial})
# If Serial is Good obtain fireplace and configure
fireplace = self.cloud_api_interface.user_data.get_data_for_serial(serial)
if fireplace:
return await self._async_create_config_entry_from_common_data(
fireplace=fireplace
)
# Parse User Data to see if we auto-configure or prompt for selection:
user_data = self.cloud_api_interface.user_data
available_fireplaces: list[IntelliFireCommonFireplaceData] = [
fp
for fp in user_data.fireplaces
if fp.serial not in self._configured_serials
]
# Abort if all devices have been configured
if not available_fireplaces:
return self.async_abort(reason="no_available_devices")
# If there is a single fireplace configure it
if len(available_fireplaces) == 1:
if self._is_reauth:
reauth_entry = self.hass.config_entries.async_get_entry(
self.context["entry_id"]
)
return await self._async_create_config_entry_from_common_data(
fireplace=available_fireplaces[0], existing_entry=reauth_entry
)
return await self._async_create_config_entry_from_common_data(
fireplace=available_fireplaces[0]
)
return self.async_show_form(
step_id="pick_cloud_device",
errors=errors,
data_schema=vol.Schema(
{
vol.Required(CONF_SERIAL): vol.In(
[fp.serial for fp in available_fireplaces]
)
}
),
)
async def _async_create_config_entry_from_common_data(
self,
fireplace: IntelliFireCommonFireplaceData,
existing_entry: ConfigEntry | None = None,
) -> ConfigFlowResult:
"""Construct a config entry based on an object of IntelliFireCommonFireplaceData."""
data = {
CONF_IP_ADDRESS: fireplace.ip_address,
CONF_API_KEY: fireplace.api_key,
CONF_SERIAL: fireplace.serial,
CONF_AUTH_COOKIE: fireplace.auth_cookie,
CONF_WEB_CLIENT_ID: fireplace.web_client_id,
CONF_USER_ID: fireplace.user_id,
CONF_USERNAME: self.cloud_api_interface.user_data.username,
CONF_PASSWORD: self.cloud_api_interface.user_data.password,
}
options = {CONF_READ_MODE: API_MODE_LOCAL, CONF_CONTROL_MODE: API_MODE_LOCAL}
if existing_entry:
return self.async_update_reload_and_abort(
existing_entry, data=data, options=options
)
return self.async_create_entry(
title=f"Fireplace {fireplace.serial}", data=data, options=options
)
async def async_step_reauth(
self, entry_data: Mapping[str, Any]
) -> ConfigFlowResult:
"""Perform reauth upon an API authentication error."""
LOGGER.debug("STEP: reauth")
self._is_reauth = True
entry = self.hass.config_entries.async_get_entry(self.context["entry_id"])
assert entry
assert entry.unique_id
# populate the expected vars
self._serial = entry.unique_id
self._host = entry.data[CONF_HOST]
self._dhcp_discovered_serial = entry.data[CONF_SERIAL] # type: ignore[union-attr]
placeholders = {CONF_HOST: self._host, "serial": self._serial}
placeholders = {"serial": self._dhcp_discovered_serial}
self.context["title_placeholders"] = placeholders
return await self.async_step_api_config()
return await self.async_step_cloud_api()
async def async_step_dhcp(
self, discovery_info: DhcpServiceInfo
) -> ConfigFlowResult:
"""Handle DHCP Discovery."""
self._dhcp_mode = True
# Run validation logic on ip
host = discovery_info.ip
LOGGER.debug("STEP: dhcp for host %s", host)
ip_address = discovery_info.ip
LOGGER.debug("STEP: dhcp for ip_address %s", ip_address)
self._async_abort_entries_match({CONF_HOST: host})
self._async_abort_entries_match({CONF_IP_ADDRESS: ip_address})
try:
self._serial = await validate_host_input(host, dhcp_mode=True)
self._dhcp_discovered_serial = await _async_poll_local_fireplace_for_serial(
ip_address, dhcp_mode=True
)
except (ConnectionError, ClientConnectionError):
LOGGER.debug(
"DHCP Discovery has determined %s is not an IntelliFire device", host
"DHCP Discovery has determined %s is not an IntelliFire device",
ip_address,
)
return self.async_abort(reason="not_intellifire_device")
await self.async_set_unique_id(self._serial)
self._abort_if_unique_id_configured(updates={CONF_HOST: host})
self._discovered_host = DiscoveredHostInfo(ip=host, serial=self._serial)
placeholders = {CONF_HOST: host, "serial": self._serial}
self.context["title_placeholders"] = placeholders
self._set_confirm_only()
return await self.async_step_dhcp_confirm()
async def async_step_dhcp_confirm(self, user_input=None):
"""Attempt to confirm."""
LOGGER.debug("STEP: dhcp_confirm")
# Add the hosts one by one
host = self._discovered_host.ip
serial = self._discovered_host.serial
if user_input is None:
# Show the confirmation dialog
return self.async_show_form(
step_id="dhcp_confirm",
description_placeholders={CONF_HOST: host, "serial": serial},
)
return self.async_create_entry(
title=f"Fireplace {serial}",
data={CONF_HOST: host},
)
return await self.async_step_cloud_api()

View File

@ -5,11 +5,22 @@ from __future__ import annotations
import logging
DOMAIN = "intellifire"
CONF_USER_ID = "user_id"
LOGGER = logging.getLogger(__package__)
DEFAULT_THERMOSTAT_TEMP = 21
CONF_USER_ID = "user_id" # part of the cloud cookie
CONF_WEB_CLIENT_ID = "web_client_id" # part of the cloud cookie
CONF_AUTH_COOKIE = "auth_cookie" # part of the cloud cookie
CONF_SERIAL = "serial"
CONF_READ_MODE = "cloud_read"
CONF_CONTROL_MODE = "cloud_control"
DEFAULT_THERMOSTAT_TEMP = 21
API_MODE_LOCAL = "local"
API_MODE_CLOUD = "cloud"
STARTUP_TIMEOUT = 600
INIT_WAIT_TIME_SECONDS = 10

View File

@ -2,27 +2,27 @@
from __future__ import annotations
import asyncio
from datetime import timedelta
from aiohttp import ClientConnectionError
from intellifire4py import IntellifirePollData
from intellifire4py.intellifire import IntellifireAPILocal
from intellifire4py import UnifiedFireplace
from intellifire4py.control import IntelliFireController
from intellifire4py.model import IntelliFirePollData
from intellifire4py.read import IntelliFireDataProvider
from homeassistant.core import HomeAssistant
from homeassistant.helpers.device_registry import DeviceInfo
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
from .const import DOMAIN, LOGGER
class IntellifireDataUpdateCoordinator(DataUpdateCoordinator[IntellifirePollData]):
class IntellifireDataUpdateCoordinator(DataUpdateCoordinator[IntelliFirePollData]):
"""Class to manage the polling of the fireplace API."""
def __init__(
self,
hass: HomeAssistant,
api: IntellifireAPILocal,
fireplace: UnifiedFireplace,
) -> None:
"""Initialize the Coordinator."""
super().__init__(
@ -31,36 +31,21 @@ class IntellifireDataUpdateCoordinator(DataUpdateCoordinator[IntellifirePollData
name=DOMAIN,
update_interval=timedelta(seconds=15),
)
self._api = api
async def _async_update_data(self) -> IntellifirePollData:
if not self._api.is_polling_in_background:
LOGGER.info("Starting Intellifire Background Polling Loop")
await self._api.start_background_polling()
# Don't return uninitialized poll data
async with asyncio.timeout(15):
try:
await self._api.poll()
except (ConnectionError, ClientConnectionError) as exception:
raise UpdateFailed from exception
LOGGER.debug("Failure Count %d", self._api.failed_poll_attempts)
if self._api.failed_poll_attempts > 10:
LOGGER.debug("Too many polling errors - raising exception")
raise UpdateFailed
return self._api.data
self.fireplace = fireplace
@property
def read_api(self) -> IntellifireAPILocal:
def read_api(self) -> IntelliFireDataProvider:
"""Return the Status API pointer."""
return self._api
return self.fireplace.read_api
@property
def control_api(self) -> IntellifireAPILocal:
def control_api(self) -> IntelliFireController:
"""Return the control API."""
return self._api
return self.fireplace.control_api
async def _async_update_data(self) -> IntelliFirePollData:
return self.fireplace.data
@property
def device_info(self) -> DeviceInfo:
@ -69,7 +54,6 @@ class IntellifireDataUpdateCoordinator(DataUpdateCoordinator[IntellifirePollData
manufacturer="Hearth and Home",
model="IFT-WFM",
name="IntelliFire",
identifiers={("IntelliFire", f"{self.read_api.data.serial}]")},
sw_version=self.read_api.data.fw_ver_str,
configuration_url=f"http://{self._api.fireplace_ip}/poll",
identifiers={("IntelliFire", str(self.fireplace.serial))},
configuration_url=f"http://{self.fireplace.ip_address}/poll",
)

View File

@ -9,7 +9,7 @@ from . import IntellifireDataUpdateCoordinator
class IntellifireEntity(CoordinatorEntity[IntellifireDataUpdateCoordinator]):
"""Define a generic class for Intellifire entities."""
"""Define a generic class for IntelliFire entities."""
_attr_attribution = "Data provided by unpublished Intellifire API"
_attr_has_entity_name = True
@ -22,6 +22,8 @@ class IntellifireEntity(CoordinatorEntity[IntellifireDataUpdateCoordinator]):
"""Class initializer."""
super().__init__(coordinator=coordinator)
self.entity_description = description
self._attr_unique_id = f"{description.key}_{coordinator.read_api.data.serial}"
self._attr_unique_id = f"{description.key}_{coordinator.fireplace.serial}"
self.identifiers = ({("IntelliFire", f"{coordinator.fireplace.serial}]")},)
# Configure the Device Info
self._attr_device_info = self.coordinator.device_info

View File

@ -7,7 +7,8 @@ from dataclasses import dataclass
import math
from typing import Any
from intellifire4py import IntellifireControlAsync, IntellifirePollData
from intellifire4py.control import IntelliFireController
from intellifire4py.model import IntelliFirePollData
from homeassistant.components.fan import (
FanEntity,
@ -31,8 +32,8 @@ from .entity import IntellifireEntity
class IntellifireFanRequiredKeysMixin:
"""Required keys for fan entity."""
set_fn: Callable[[IntellifireControlAsync, int], Awaitable]
value_fn: Callable[[IntellifirePollData], bool]
set_fn: Callable[[IntelliFireController, int], Awaitable]
value_fn: Callable[[IntelliFirePollData], int]
speed_range: tuple[int, int]
@ -91,7 +92,8 @@ class IntellifireFan(IntellifireEntity, FanEntity):
def percentage(self) -> int | None:
"""Return fan percentage."""
return ranged_value_to_percentage(
self.entity_description.speed_range, self.coordinator.read_api.data.fanspeed
self.entity_description.speed_range,
self.coordinator.read_api.data.fanspeed,
)
@property

View File

@ -6,7 +6,8 @@ from collections.abc import Awaitable, Callable
from dataclasses import dataclass
from typing import Any
from intellifire4py import IntellifireControlAsync, IntellifirePollData
from intellifire4py.control import IntelliFireController
from intellifire4py.model import IntelliFirePollData
from homeassistant.components.light import (
ATTR_BRIGHTNESS,
@ -27,8 +28,8 @@ from .entity import IntellifireEntity
class IntellifireLightRequiredKeysMixin:
"""Required keys for fan entity."""
set_fn: Callable[[IntellifireControlAsync, int], Awaitable]
value_fn: Callable[[IntellifirePollData], bool]
set_fn: Callable[[IntelliFireController, int], Awaitable]
value_fn: Callable[[IntelliFirePollData], int]
@dataclass(frozen=True)
@ -56,7 +57,7 @@ class IntellifireLight(IntellifireEntity, LightEntity):
_attr_supported_color_modes = {ColorMode.BRIGHTNESS}
@property
def brightness(self):
def brightness(self) -> int:
"""Return the current brightness 0-255."""
return 85 * self.entity_description.value_fn(self.coordinator.read_api.data)

View File

@ -11,5 +11,5 @@
"documentation": "https://www.home-assistant.io/integrations/intellifire",
"iot_class": "local_polling",
"loggers": ["intellifire4py"],
"requirements": ["intellifire4py==2.2.2"]
"requirements": ["intellifire4py==4.1.9"]
}

View File

@ -6,8 +6,6 @@ from collections.abc import Callable
from dataclasses import dataclass
from datetime import datetime, timedelta
from intellifire4py import IntellifirePollData
from homeassistant.components.sensor import (
SensorDeviceClass,
SensorEntity,
@ -29,7 +27,9 @@ from .entity import IntellifireEntity
class IntellifireSensorRequiredKeysMixin:
"""Mixin for required keys."""
value_fn: Callable[[IntellifirePollData], int | str | datetime | None]
value_fn: Callable[
[IntellifireDataUpdateCoordinator], int | str | datetime | float | None
]
@dataclass(frozen=True)
@ -40,16 +40,29 @@ class IntellifireSensorEntityDescription(
"""Describes a sensor entity."""
def _time_remaining_to_timestamp(data: IntellifirePollData) -> datetime | None:
def _time_remaining_to_timestamp(
coordinator: IntellifireDataUpdateCoordinator,
) -> datetime | None:
"""Define a sensor that takes into account timezone."""
if not (seconds_offset := data.timeremaining_s):
if not (seconds_offset := coordinator.data.timeremaining_s):
return None
return utcnow() + timedelta(seconds=seconds_offset)
def _downtime_to_timestamp(data: IntellifirePollData) -> datetime | None:
def _downtime_to_timestamp(
coordinator: IntellifireDataUpdateCoordinator,
) -> datetime | None:
"""Define a sensor that takes into account a timezone."""
if not (seconds_offset := data.downtime):
if not (seconds_offset := coordinator.data.downtime):
return None
return utcnow() - timedelta(seconds=seconds_offset)
def _uptime_to_timestamp(
coordinator: IntellifireDataUpdateCoordinator,
) -> datetime | None:
"""Return a timestamp of how long the sensor has been up."""
if not (seconds_offset := coordinator.data.uptime):
return None
return utcnow() - timedelta(seconds=seconds_offset)
@ -60,14 +73,14 @@ INTELLIFIRE_SENSORS: tuple[IntellifireSensorEntityDescription, ...] = (
translation_key="flame_height",
state_class=SensorStateClass.MEASUREMENT,
# UI uses 1-5 for flame height, backing lib uses 0-4
value_fn=lambda data: (data.flameheight + 1),
value_fn=lambda coordinator: (coordinator.data.flameheight + 1),
),
IntellifireSensorEntityDescription(
key="temperature",
state_class=SensorStateClass.MEASUREMENT,
device_class=SensorDeviceClass.TEMPERATURE,
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
value_fn=lambda data: data.temperature_c,
value_fn=lambda coordinator: coordinator.data.temperature_c,
),
IntellifireSensorEntityDescription(
key="target_temp",
@ -75,13 +88,13 @@ INTELLIFIRE_SENSORS: tuple[IntellifireSensorEntityDescription, ...] = (
state_class=SensorStateClass.MEASUREMENT,
device_class=SensorDeviceClass.TEMPERATURE,
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
value_fn=lambda data: data.thermostat_setpoint_c,
value_fn=lambda coordinator: coordinator.data.thermostat_setpoint_c,
),
IntellifireSensorEntityDescription(
key="fan_speed",
translation_key="fan_speed",
state_class=SensorStateClass.MEASUREMENT,
value_fn=lambda data: data.fanspeed,
value_fn=lambda coordinator: coordinator.data.fanspeed,
),
IntellifireSensorEntityDescription(
key="timer_end_timestamp",
@ -102,27 +115,27 @@ INTELLIFIRE_SENSORS: tuple[IntellifireSensorEntityDescription, ...] = (
translation_key="uptime",
entity_category=EntityCategory.DIAGNOSTIC,
device_class=SensorDeviceClass.TIMESTAMP,
value_fn=lambda data: utcnow() - timedelta(seconds=data.uptime),
value_fn=_uptime_to_timestamp,
),
IntellifireSensorEntityDescription(
key="connection_quality",
translation_key="connection_quality",
entity_category=EntityCategory.DIAGNOSTIC,
value_fn=lambda data: data.connection_quality,
value_fn=lambda coordinator: coordinator.data.connection_quality,
entity_registry_enabled_default=False,
),
IntellifireSensorEntityDescription(
key="ecm_latency",
translation_key="ecm_latency",
entity_category=EntityCategory.DIAGNOSTIC,
value_fn=lambda data: data.ecm_latency,
value_fn=lambda coordinator: coordinator.data.ecm_latency,
entity_registry_enabled_default=False,
),
IntellifireSensorEntityDescription(
key="ipv4_address",
translation_key="ipv4_address",
entity_category=EntityCategory.DIAGNOSTIC,
value_fn=lambda data: data.ipv4_address,
value_fn=lambda coordinator: coordinator.data.ipv4_address,
),
)
@ -134,17 +147,17 @@ async def async_setup_entry(
coordinator: IntellifireDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id]
async_add_entities(
IntellifireSensor(coordinator=coordinator, description=description)
IntelliFireSensor(coordinator=coordinator, description=description)
for description in INTELLIFIRE_SENSORS
)
class IntellifireSensor(IntellifireEntity, SensorEntity):
"""Extends IntellifireEntity with Sensor specific logic."""
class IntelliFireSensor(IntellifireEntity, SensorEntity):
"""Extends IntelliFireEntity with Sensor specific logic."""
entity_description: IntellifireSensorEntityDescription
@property
def native_value(self) -> int | str | datetime | None:
def native_value(self) -> int | str | datetime | float | None:
"""Return the state."""
return self.entity_description.value_fn(self.coordinator.read_api.data)
return self.entity_description.value_fn(self.coordinator)

View File

@ -1,39 +1,30 @@
{
"config": {
"flow_title": "{serial} ({host})",
"flow_title": "{serial}",
"step": {
"manual_device_entry": {
"description": "Local Configuration",
"data": {
"host": "Host (IP Address)"
}
"pick_cloud_device": {
"title": "Configure fireplace",
"description": "Select fireplace by serial number:"
},
"api_config": {
"cloud_api": {
"description": "Authenticate against IntelliFire Cloud",
"data_description": {
"username": "Your IntelliFire app username",
"password": "Your IntelliFire app password"
},
"data": {
"username": "[%key:common::config_flow::data::email%]",
"password": "[%key:common::config_flow::data::password%]"
}
},
"dhcp_confirm": {
"description": "Do you want to set up {host}\nSerial: {serial}?"
},
"pick_device": {
"title": "Device Selection",
"description": "The following IntelliFire devices were discovered. Please select which you wish to configure.",
"data": {
"host": "[%key:common::config_flow::data::host%]"
}
}
},
"error": {
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
"api_error": "Login failed",
"iftapi_connect": "Error conecting to iftapi.net"
"api_error": "Login failed"
},
"abort": {
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]",
"reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]",
"not_intellifire_device": "Not an IntelliFire Device."
"not_intellifire_device": "Not an IntelliFire device.",
"no_available_devices": "All available devices have already been configured."
}
},
"entity": {

View File

@ -6,16 +6,13 @@ from collections.abc import Awaitable, Callable
from dataclasses import dataclass
from typing import Any
from intellifire4py import IntellifirePollData
from intellifire4py.intellifire import IntellifireAPILocal
from homeassistant.components.switch import SwitchEntity, SwitchEntityDescription
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from . import IntellifireDataUpdateCoordinator
from .const import DOMAIN
from .coordinator import IntellifireDataUpdateCoordinator
from .entity import IntellifireEntity
@ -23,9 +20,9 @@ from .entity import IntellifireEntity
class IntellifireSwitchRequiredKeysMixin:
"""Mixin for required keys."""
on_fn: Callable[[IntellifireAPILocal], Awaitable]
off_fn: Callable[[IntellifireAPILocal], Awaitable]
value_fn: Callable[[IntellifirePollData], bool]
on_fn: Callable[[IntellifireDataUpdateCoordinator], Awaitable]
off_fn: Callable[[IntellifireDataUpdateCoordinator], Awaitable]
value_fn: Callable[[IntellifireDataUpdateCoordinator], bool]
@dataclass(frozen=True)
@ -39,16 +36,16 @@ INTELLIFIRE_SWITCHES: tuple[IntellifireSwitchEntityDescription, ...] = (
IntellifireSwitchEntityDescription(
key="on_off",
translation_key="flame",
on_fn=lambda control_api: control_api.flame_on(),
off_fn=lambda control_api: control_api.flame_off(),
value_fn=lambda data: data.is_on,
on_fn=lambda coordinator: coordinator.control_api.flame_on(),
off_fn=lambda coordinator: coordinator.control_api.flame_off(),
value_fn=lambda coordinator: coordinator.read_api.data.is_on,
),
IntellifireSwitchEntityDescription(
key="pilot",
translation_key="pilot_light",
on_fn=lambda control_api: control_api.pilot_on(),
off_fn=lambda control_api: control_api.pilot_off(),
value_fn=lambda data: data.pilot_on,
on_fn=lambda coordinator: coordinator.control_api.pilot_on(),
off_fn=lambda coordinator: coordinator.control_api.pilot_off(),
value_fn=lambda coordinator: coordinator.read_api.data.pilot_on,
),
)
@ -74,15 +71,15 @@ class IntellifireSwitch(IntellifireEntity, SwitchEntity):
async def async_turn_on(self, **kwargs: Any) -> None:
"""Turn on the switch."""
await self.entity_description.on_fn(self.coordinator.control_api)
await self.entity_description.on_fn(self.coordinator)
await self.async_update_ha_state(force_refresh=True)
async def async_turn_off(self, **kwargs: Any) -> None:
"""Turn off the switch."""
await self.entity_description.off_fn(self.coordinator.control_api)
await self.entity_description.off_fn(self.coordinator)
await self.async_update_ha_state(force_refresh=True)
@property
def is_on(self) -> bool | None:
"""Return the on state."""
return self.entity_description.value_fn(self.coordinator.read_api.data)
return self.entity_description.value_fn(self.coordinator)

View File

@ -1179,7 +1179,7 @@ inkbird-ble==0.5.8
insteon-frontend-home-assistant==0.5.0
# homeassistant.components.intellifire
intellifire4py==2.2.2
intellifire4py==4.1.9
# homeassistant.components.iotty
iottycloud==0.1.3

View File

@ -987,7 +987,7 @@ inkbird-ble==0.5.8
insteon-frontend-home-assistant==0.5.0
# homeassistant.components.intellifire
intellifire4py==2.2.2
intellifire4py==4.1.9
# homeassistant.components.iotty
iottycloud==0.1.3

View File

@ -1 +1,13 @@
"""Tests for the IntelliFire 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()

View File

@ -1,14 +1,40 @@
"""Fixtures for IntelliFire integration tests."""
from collections.abc import Generator
from unittest.mock import AsyncMock, MagicMock, Mock, patch
from unittest.mock import AsyncMock, MagicMock, Mock, PropertyMock, patch
from aiohttp.client_reqrep import ConnectionKey
from intellifire4py.const import IntelliFireApiMode
from intellifire4py.model import (
IntelliFireCommonFireplaceData,
IntelliFirePollData,
IntelliFireUserData,
)
import pytest
from homeassistant.components.intellifire.const import (
API_MODE_CLOUD,
API_MODE_LOCAL,
CONF_AUTH_COOKIE,
CONF_CONTROL_MODE,
CONF_READ_MODE,
CONF_SERIAL,
CONF_USER_ID,
CONF_WEB_CLIENT_ID,
DOMAIN,
)
from homeassistant.const import (
CONF_API_KEY,
CONF_HOST,
CONF_IP_ADDRESS,
CONF_PASSWORD,
CONF_USERNAME,
)
from tests.common import MockConfigEntry, load_json_object_fixture
@pytest.fixture
def mock_setup_entry() -> Generator[AsyncMock]:
def mock_setup_entry() -> Generator[AsyncMock, None, None]:
"""Mock setting up a config entry."""
with patch(
"homeassistant.components.intellifire.async_setup_entry", return_value=True
@ -17,44 +43,206 @@ def mock_setup_entry() -> Generator[AsyncMock]:
@pytest.fixture
def mock_fireplace_finder_none() -> Generator[MagicMock]:
def mock_fireplace_finder_none() -> Generator[None, MagicMock, None]:
"""Mock fireplace finder."""
mock_found_fireplaces = Mock()
mock_found_fireplaces.ips = []
with patch(
"homeassistant.components.intellifire.config_flow.AsyncUDPFireplaceFinder.search_fireplace"
"homeassistant.components.intellifire.config_flow.UDPFireplaceFinder.search_fireplace"
):
yield mock_found_fireplaces
@pytest.fixture
def mock_fireplace_finder_single() -> Generator[MagicMock]:
"""Mock fireplace finder."""
mock_found_fireplaces = Mock()
mock_found_fireplaces.ips = ["192.168.1.69"]
with patch(
"homeassistant.components.intellifire.config_flow.AsyncUDPFireplaceFinder.search_fireplace"
):
yield mock_found_fireplaces
def mock_config_entry_current() -> MockConfigEntry:
"""Return a mock config entry."""
return MockConfigEntry(
domain=DOMAIN,
version=1,
minor_version=2,
data={
CONF_IP_ADDRESS: "192.168.2.108",
CONF_USERNAME: "grumpypanda@china.cn",
CONF_PASSWORD: "you-stole-my-pandas",
CONF_SERIAL: "3FB284769E4736F30C8973A7ED358123",
CONF_WEB_CLIENT_ID: "FA2B1C3045601234D0AE17D72F8E975",
CONF_API_KEY: "B5C4DA27AAEF31D1FB21AFF9BFA6BCD2",
CONF_AUTH_COOKIE: "B984F21A6378560019F8A1CDE41B6782",
CONF_USER_ID: "52C3F9E8B9D3AC99F8E4D12345678901FE9A2BC7D85F7654E28BF98BCD123456",
},
options={CONF_READ_MODE: API_MODE_LOCAL, CONF_CONTROL_MODE: API_MODE_CLOUD},
unique_id="3FB284769E4736F30C8973A7ED358123",
)
@pytest.fixture
def mock_intellifire_config_flow() -> Generator[MagicMock]:
"""Return a mocked IntelliFire client."""
data_mock = Mock()
data_mock.serial = "12345"
def mock_config_entry_old() -> MockConfigEntry:
"""For migration testing."""
return MockConfigEntry(
domain=DOMAIN,
version=1,
minor_version=1,
title="Fireplace 3FB284769E4736F30C8973A7ED358123",
data={
CONF_HOST: "192.168.2.108",
CONF_USERNAME: "grumpypanda@china.cn",
CONF_PASSWORD: "you-stole-my-pandas",
CONF_USER_ID: "52C3F9E8B9D3AC99F8E4D12345678901FE9A2BC7D85F7654E28BF98BCD123456",
},
)
@pytest.fixture
def mock_common_data_local() -> IntelliFireCommonFireplaceData:
"""Fixture for mock common data."""
return IntelliFireCommonFireplaceData(
auth_cookie="B984F21A6378560019F8A1CDE41B6782",
user_id="52C3F9E8B9D3AC99F8E4D12345678901FE9A2BC7D85F7654E28BF98BCD123456",
web_client_id="FA2B1C3045601234D0AE17D72F8E975",
serial="3FB284769E4736F30C8973A7ED358123",
api_key="B5C4DA27AAEF31D1FB21AFF9BFA6BCD2",
ip_address="192.168.2.108",
read_mode=IntelliFireApiMode.LOCAL,
control_mode=IntelliFireApiMode.LOCAL,
)
@pytest.fixture
def mock_apis_multifp(
mock_cloud_interface, mock_local_interface, mock_fp
) -> Generator[tuple[AsyncMock, AsyncMock, MagicMock], None, None]:
"""Multi fireplace version of mocks."""
return mock_local_interface, mock_cloud_interface, mock_fp
@pytest.fixture
def mock_apis_single_fp(
mock_cloud_interface, mock_local_interface, mock_fp
) -> Generator[tuple[AsyncMock, AsyncMock, MagicMock], None, None]:
"""Single fire place version of the mocks."""
data_v1 = IntelliFireUserData(
**load_json_object_fixture("user_data_1.json", DOMAIN)
)
with patch.object(
type(mock_cloud_interface), "user_data", new_callable=PropertyMock
) as mock_user_data:
mock_user_data.return_value = data_v1
yield mock_local_interface, mock_cloud_interface, mock_fp
@pytest.fixture
def mock_cloud_interface() -> Generator[AsyncMock, None, None]:
"""Mock cloud interface to use for testing."""
user_data = IntelliFireUserData(
**load_json_object_fixture("user_data_3.json", DOMAIN)
)
with (
patch(
"homeassistant.components.intellifire.IntelliFireCloudInterface",
autospec=True,
) as mock_client,
patch(
"homeassistant.components.intellifire.config_flow.IntelliFireCloudInterface",
new=mock_client,
),
patch(
"intellifire4py.cloud_interface.IntelliFireCloudInterface",
new=mock_client,
),
):
# Mock async context manager
mock_client = mock_client.return_value
mock_client.__aenter__ = AsyncMock(return_value=mock_client)
mock_client.__aexit__ = AsyncMock(return_value=None)
# Mock other async methods if needed
mock_client.login_with_credentials = AsyncMock()
mock_client.poll = AsyncMock()
type(mock_client).user_data = PropertyMock(return_value=user_data)
yield mock_client # Yielding to the test
@pytest.fixture
def mock_local_interface() -> Generator[AsyncMock, None, None]:
"""Mock version of IntelliFireAPILocal."""
poll_data = IntelliFirePollData(
**load_json_object_fixture("intellifire/local_poll.json")
)
with patch(
"homeassistant.components.intellifire.config_flow.IntellifireAPILocal",
"homeassistant.components.intellifire.config_flow.IntelliFireAPILocal",
autospec=True,
) as intellifire_mock:
intellifire = intellifire_mock.return_value
intellifire.data = data_mock
yield intellifire
) as mock_client:
mock_client = mock_client.return_value
# Mock all instances of the class
type(mock_client).data = PropertyMock(return_value=poll_data)
yield mock_client
def mock_api_connection_error() -> ConnectionError:
"""Return a fake a ConnectionError for iftapi.net."""
ret = ConnectionError()
ret.args = [ConnectionKey("iftapi.net", 443, False, None, None, None, None)]
return ret
@pytest.fixture
def mock_fp(mock_common_data_local) -> Generator[AsyncMock, None, None]:
"""Mock fireplace."""
local_poll_data = IntelliFirePollData(
**load_json_object_fixture("local_poll.json", DOMAIN)
)
assert local_poll_data.connection_quality == 988451
with patch(
"homeassistant.components.intellifire.UnifiedFireplace"
) as mock_unified_fireplace:
# Create an instance of the mock
mock_instance = mock_unified_fireplace.return_value
# Mock methods and properties of the instance
mock_instance.perform_cloud_poll = AsyncMock()
mock_instance.perform_local_poll = AsyncMock()
mock_instance.async_validate_connectivity = AsyncMock(return_value=(True, True))
type(mock_instance).is_cloud_polling = PropertyMock(return_value=False)
type(mock_instance).is_local_polling = PropertyMock(return_value=True)
mock_instance.get_user_data_as_json.return_value = '{"mock": "data"}'
mock_instance.ip_address = "192.168.1.100"
mock_instance.api_key = "mock_api_key"
mock_instance.serial = "mock_serial"
mock_instance.user_id = "mock_user_id"
mock_instance.auth_cookie = "mock_auth_cookie"
mock_instance.web_client_id = "mock_web_client_id"
# Configure the READ Api
mock_instance.read_api = MagicMock()
mock_instance.read_api.poll = MagicMock(return_value=local_poll_data)
mock_instance.read_api.data = local_poll_data
mock_instance.control_api = MagicMock()
mock_instance.local_connectivity = True
mock_instance.cloud_connectivity = False
mock_instance._read_mode = IntelliFireApiMode.LOCAL
mock_instance.read_mode = IntelliFireApiMode.LOCAL
mock_instance.control_mode = IntelliFireApiMode.LOCAL
mock_instance._control_mode = IntelliFireApiMode.LOCAL
mock_instance.data = local_poll_data
mock_instance.set_read_mode = AsyncMock()
mock_instance.set_control_mode = AsyncMock()
mock_instance.async_validate_connectivity = AsyncMock(
return_value=(True, False)
)
# Patch class methods
with patch(
"homeassistant.components.intellifire.UnifiedFireplace.build_fireplace_from_common",
new_callable=AsyncMock,
return_value=mock_instance,
):
yield mock_instance

View File

@ -0,0 +1,29 @@
{
"name": "",
"serial": "4GC295860E5837G40D9974B7FD459234",
"temperature": 17,
"battery": 0,
"pilot": 1,
"light": 0,
"height": 1,
"fanspeed": 1,
"hot": 0,
"power": 1,
"thermostat": 0,
"setpoint": 0,
"timer": 0,
"timeremaining": 0,
"prepurge": 0,
"feature_light": 0,
"feature_thermostat": 1,
"power_vent": 0,
"feature_fan": 1,
"errors": [],
"fw_version": "0x00030200",
"fw_ver_str": "0.3.2+hw2",
"downtime": 0,
"uptime": 117,
"connection_quality": 988451,
"ecm_latency": 0,
"ipv4_address": "192.168.2.108"
}

View File

@ -0,0 +1,17 @@
{
"auth_cookie": "B984F21A6378560019F8A1CDE41B6782",
"user_id": "52C3F9E8B9D3AC99F8E4D12345678901FE9A2BC7D85F7654E28BF98BCD123456",
"web_client_id": "FA2B1C3045601234D0AE17D72F8E975",
"fireplaces": [
{
"auth_cookie": "B984F21A6378560019F8A1CDE41B6782",
"user_id": "52C3F9E8B9D3AC99F8E4D12345678901FE9A2BC7D85F7654E28BF98BCD123456",
"web_client_id": "FA2B1C3045601234D0AE17D72F8E975",
"ip_address": "192.168.2.108",
"api_key": "B5C4DA27AAEF31D1FB21AFF9BFA6BCD2",
"serial": "3FB284769E4736F30C8973A7ED358123"
}
],
"username": "grumpypanda@china.cn",
"password": "you-stole-my-pandas"
}

View File

@ -0,0 +1,33 @@
{
"auth_cookie": "B984F21A6378560019F8A1CDE41B6782",
"user_id": "52C3F9E8B9D3AC99F8E4D12345678901FE9A2BC7D85F7654E28BF98BCD123456",
"web_client_id": "FA2B1C3045601234D0AE17D72F8E975",
"fireplaces": [
{
"auth_cookie": "B984F21A6378560019F8A1CDE41B6782",
"user_id": "52C3F9E8B9D3AC99F8E4D12345678901FE9A2BC7D85F7654E28BF98BCD123456",
"web_client_id": "FA2B1C3045601234D0AE17D72F8E975",
"ip_address": "192.168.2.108",
"api_key": "B5C4DA27AAEF31D1FB21AFF9BFA6BCD2",
"serial": "3FB284769E4736F30C8973A7ED358123"
},
{
"auth_cookie": "B984F21A6378560019F8A1CDE41B6782",
"user_id": "52C3F9E8B9D3AC99F8E4D12345678901FE9A2BC7D85F7654E28BF98BCD123456",
"web_client_id": "FA2B1C3045601234D0AE17D72F8E975",
"ip_address": "192.168.2.109",
"api_key": "D4C5EB28BBFF41E1FB21AFF9BFA6CD34",
"serial": "4GC295860E5837G40D9974B7FD459234"
},
{
"auth_cookie": "B984F21A6378560019F8A1CDE41B6782",
"user_id": "52C3F9E8B9D3AC99F8E4D12345678901FE9A2BC7D85F7654E28BF98BCD123456",
"web_client_id": "FA2B1C3045601234D0AE17D72F8E975",
"ip_address": "192.168.2.110",
"api_key": "E5D6FC39CCED52F1FB21AFF9BFA6DE56",
"serial": "5HD306971F5938H51EAA85C8GE561345"
}
],
"username": "grumpypanda@china.cn",
"password": "you-stole-my-pandas"
}

View File

@ -0,0 +1,717 @@
# serializer version: 1
# name: test_all_binary_sensor_entities[binary_sensor.intellifire_accessory_error-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'binary_sensor',
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
'entity_id': 'binary_sensor.intellifire_accessory_error',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': <BinarySensorDeviceClass.PROBLEM: 'problem'>,
'original_icon': None,
'original_name': 'Accessory error',
'platform': 'intellifire',
'previous_unique_id': None,
'supported_features': 0,
'translation_key': 'accessory_error',
'unique_id': 'error_accessory_mock_serial',
'unit_of_measurement': None,
})
# ---
# name: test_all_binary_sensor_entities[binary_sensor.intellifire_accessory_error-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'attribution': 'Data provided by unpublished Intellifire API',
'device_class': 'problem',
'friendly_name': 'IntelliFire Accessory error',
}),
'context': <ANY>,
'entity_id': 'binary_sensor.intellifire_accessory_error',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'off',
})
# ---
# name: test_all_binary_sensor_entities[binary_sensor.intellifire_disabled_error-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'binary_sensor',
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
'entity_id': 'binary_sensor.intellifire_disabled_error',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': <BinarySensorDeviceClass.PROBLEM: 'problem'>,
'original_icon': None,
'original_name': 'Disabled error',
'platform': 'intellifire',
'previous_unique_id': None,
'supported_features': 0,
'translation_key': 'disabled_error',
'unique_id': 'error_disabled_mock_serial',
'unit_of_measurement': None,
})
# ---
# name: test_all_binary_sensor_entities[binary_sensor.intellifire_disabled_error-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'attribution': 'Data provided by unpublished Intellifire API',
'device_class': 'problem',
'friendly_name': 'IntelliFire Disabled error',
}),
'context': <ANY>,
'entity_id': 'binary_sensor.intellifire_disabled_error',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'off',
})
# ---
# name: test_all_binary_sensor_entities[binary_sensor.intellifire_ecm_offline_error-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'binary_sensor',
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
'entity_id': 'binary_sensor.intellifire_ecm_offline_error',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': <BinarySensorDeviceClass.PROBLEM: 'problem'>,
'original_icon': None,
'original_name': 'ECM offline error',
'platform': 'intellifire',
'previous_unique_id': None,
'supported_features': 0,
'translation_key': 'ecm_offline_error',
'unique_id': 'error_ecm_offline_mock_serial',
'unit_of_measurement': None,
})
# ---
# name: test_all_binary_sensor_entities[binary_sensor.intellifire_ecm_offline_error-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'attribution': 'Data provided by unpublished Intellifire API',
'device_class': 'problem',
'friendly_name': 'IntelliFire ECM offline error',
}),
'context': <ANY>,
'entity_id': 'binary_sensor.intellifire_ecm_offline_error',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'off',
})
# ---
# name: test_all_binary_sensor_entities[binary_sensor.intellifire_fan_delay_error-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'binary_sensor',
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
'entity_id': 'binary_sensor.intellifire_fan_delay_error',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': <BinarySensorDeviceClass.PROBLEM: 'problem'>,
'original_icon': None,
'original_name': 'Fan delay error',
'platform': 'intellifire',
'previous_unique_id': None,
'supported_features': 0,
'translation_key': 'fan_delay_error',
'unique_id': 'error_fan_delay_mock_serial',
'unit_of_measurement': None,
})
# ---
# name: test_all_binary_sensor_entities[binary_sensor.intellifire_fan_delay_error-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'attribution': 'Data provided by unpublished Intellifire API',
'device_class': 'problem',
'friendly_name': 'IntelliFire Fan delay error',
}),
'context': <ANY>,
'entity_id': 'binary_sensor.intellifire_fan_delay_error',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'off',
})
# ---
# name: test_all_binary_sensor_entities[binary_sensor.intellifire_fan_error-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'binary_sensor',
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
'entity_id': 'binary_sensor.intellifire_fan_error',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': <BinarySensorDeviceClass.PROBLEM: 'problem'>,
'original_icon': None,
'original_name': 'Fan error',
'platform': 'intellifire',
'previous_unique_id': None,
'supported_features': 0,
'translation_key': 'fan_error',
'unique_id': 'error_fan_mock_serial',
'unit_of_measurement': None,
})
# ---
# name: test_all_binary_sensor_entities[binary_sensor.intellifire_fan_error-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'attribution': 'Data provided by unpublished Intellifire API',
'device_class': 'problem',
'friendly_name': 'IntelliFire Fan error',
}),
'context': <ANY>,
'entity_id': 'binary_sensor.intellifire_fan_error',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'off',
})
# ---
# name: test_all_binary_sensor_entities[binary_sensor.intellifire_flame-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'binary_sensor',
'entity_category': None,
'entity_id': 'binary_sensor.intellifire_flame',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'Flame',
'platform': 'intellifire',
'previous_unique_id': None,
'supported_features': 0,
'translation_key': 'flame',
'unique_id': 'on_off_mock_serial',
'unit_of_measurement': None,
})
# ---
# name: test_all_binary_sensor_entities[binary_sensor.intellifire_flame-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'attribution': 'Data provided by unpublished Intellifire API',
'friendly_name': 'IntelliFire Flame',
}),
'context': <ANY>,
'entity_id': 'binary_sensor.intellifire_flame',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'on',
})
# ---
# name: test_all_binary_sensor_entities[binary_sensor.intellifire_flame_error-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'binary_sensor',
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
'entity_id': 'binary_sensor.intellifire_flame_error',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': <BinarySensorDeviceClass.PROBLEM: 'problem'>,
'original_icon': None,
'original_name': 'Flame Error',
'platform': 'intellifire',
'previous_unique_id': None,
'supported_features': 0,
'translation_key': 'flame_error',
'unique_id': 'error_flame_mock_serial',
'unit_of_measurement': None,
})
# ---
# name: test_all_binary_sensor_entities[binary_sensor.intellifire_flame_error-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'attribution': 'Data provided by unpublished Intellifire API',
'device_class': 'problem',
'friendly_name': 'IntelliFire Flame Error',
}),
'context': <ANY>,
'entity_id': 'binary_sensor.intellifire_flame_error',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'off',
})
# ---
# name: test_all_binary_sensor_entities[binary_sensor.intellifire_lights_error-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'binary_sensor',
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
'entity_id': 'binary_sensor.intellifire_lights_error',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': <BinarySensorDeviceClass.PROBLEM: 'problem'>,
'original_icon': None,
'original_name': 'Lights error',
'platform': 'intellifire',
'previous_unique_id': None,
'supported_features': 0,
'translation_key': 'lights_error',
'unique_id': 'error_lights_mock_serial',
'unit_of_measurement': None,
})
# ---
# name: test_all_binary_sensor_entities[binary_sensor.intellifire_lights_error-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'attribution': 'Data provided by unpublished Intellifire API',
'device_class': 'problem',
'friendly_name': 'IntelliFire Lights error',
}),
'context': <ANY>,
'entity_id': 'binary_sensor.intellifire_lights_error',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'off',
})
# ---
# name: test_all_binary_sensor_entities[binary_sensor.intellifire_maintenance_error-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'binary_sensor',
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
'entity_id': 'binary_sensor.intellifire_maintenance_error',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': <BinarySensorDeviceClass.PROBLEM: 'problem'>,
'original_icon': None,
'original_name': 'Maintenance error',
'platform': 'intellifire',
'previous_unique_id': None,
'supported_features': 0,
'translation_key': 'maintenance_error',
'unique_id': 'error_maintenance_mock_serial',
'unit_of_measurement': None,
})
# ---
# name: test_all_binary_sensor_entities[binary_sensor.intellifire_maintenance_error-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'attribution': 'Data provided by unpublished Intellifire API',
'device_class': 'problem',
'friendly_name': 'IntelliFire Maintenance error',
}),
'context': <ANY>,
'entity_id': 'binary_sensor.intellifire_maintenance_error',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'off',
})
# ---
# name: test_all_binary_sensor_entities[binary_sensor.intellifire_offline_error-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'binary_sensor',
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
'entity_id': 'binary_sensor.intellifire_offline_error',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': <BinarySensorDeviceClass.PROBLEM: 'problem'>,
'original_icon': None,
'original_name': 'Offline error',
'platform': 'intellifire',
'previous_unique_id': None,
'supported_features': 0,
'translation_key': 'offline_error',
'unique_id': 'error_offline_mock_serial',
'unit_of_measurement': None,
})
# ---
# name: test_all_binary_sensor_entities[binary_sensor.intellifire_offline_error-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'attribution': 'Data provided by unpublished Intellifire API',
'device_class': 'problem',
'friendly_name': 'IntelliFire Offline error',
}),
'context': <ANY>,
'entity_id': 'binary_sensor.intellifire_offline_error',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'off',
})
# ---
# name: test_all_binary_sensor_entities[binary_sensor.intellifire_pilot_flame_error-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'binary_sensor',
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
'entity_id': 'binary_sensor.intellifire_pilot_flame_error',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': <BinarySensorDeviceClass.PROBLEM: 'problem'>,
'original_icon': None,
'original_name': 'Pilot flame error',
'platform': 'intellifire',
'previous_unique_id': None,
'supported_features': 0,
'translation_key': 'pilot_flame_error',
'unique_id': 'error_pilot_flame_mock_serial',
'unit_of_measurement': None,
})
# ---
# name: test_all_binary_sensor_entities[binary_sensor.intellifire_pilot_flame_error-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'attribution': 'Data provided by unpublished Intellifire API',
'device_class': 'problem',
'friendly_name': 'IntelliFire Pilot flame error',
}),
'context': <ANY>,
'entity_id': 'binary_sensor.intellifire_pilot_flame_error',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'off',
})
# ---
# name: test_all_binary_sensor_entities[binary_sensor.intellifire_pilot_light_on-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'binary_sensor',
'entity_category': None,
'entity_id': 'binary_sensor.intellifire_pilot_light_on',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'Pilot light on',
'platform': 'intellifire',
'previous_unique_id': None,
'supported_features': 0,
'translation_key': 'pilot_light_on',
'unique_id': 'pilot_light_on_mock_serial',
'unit_of_measurement': None,
})
# ---
# name: test_all_binary_sensor_entities[binary_sensor.intellifire_pilot_light_on-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'attribution': 'Data provided by unpublished Intellifire API',
'friendly_name': 'IntelliFire Pilot light on',
}),
'context': <ANY>,
'entity_id': 'binary_sensor.intellifire_pilot_light_on',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'on',
})
# ---
# name: test_all_binary_sensor_entities[binary_sensor.intellifire_soft_lock_out_error-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'binary_sensor',
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
'entity_id': 'binary_sensor.intellifire_soft_lock_out_error',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': <BinarySensorDeviceClass.PROBLEM: 'problem'>,
'original_icon': None,
'original_name': 'Soft lock out error',
'platform': 'intellifire',
'previous_unique_id': None,
'supported_features': 0,
'translation_key': 'soft_lock_out_error',
'unique_id': 'error_soft_lock_out_mock_serial',
'unit_of_measurement': None,
})
# ---
# name: test_all_binary_sensor_entities[binary_sensor.intellifire_soft_lock_out_error-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'attribution': 'Data provided by unpublished Intellifire API',
'device_class': 'problem',
'friendly_name': 'IntelliFire Soft lock out error',
}),
'context': <ANY>,
'entity_id': 'binary_sensor.intellifire_soft_lock_out_error',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'off',
})
# ---
# name: test_all_binary_sensor_entities[binary_sensor.intellifire_thermostat_on-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'binary_sensor',
'entity_category': None,
'entity_id': 'binary_sensor.intellifire_thermostat_on',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'Thermostat on',
'platform': 'intellifire',
'previous_unique_id': None,
'supported_features': 0,
'translation_key': 'thermostat_on',
'unique_id': 'thermostat_on_mock_serial',
'unit_of_measurement': None,
})
# ---
# name: test_all_binary_sensor_entities[binary_sensor.intellifire_thermostat_on-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'attribution': 'Data provided by unpublished Intellifire API',
'friendly_name': 'IntelliFire Thermostat on',
}),
'context': <ANY>,
'entity_id': 'binary_sensor.intellifire_thermostat_on',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'off',
})
# ---
# name: test_all_binary_sensor_entities[binary_sensor.intellifire_timer_on-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'binary_sensor',
'entity_category': None,
'entity_id': 'binary_sensor.intellifire_timer_on',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'Timer on',
'platform': 'intellifire',
'previous_unique_id': None,
'supported_features': 0,
'translation_key': 'timer_on',
'unique_id': 'timer_on_mock_serial',
'unit_of_measurement': None,
})
# ---
# name: test_all_binary_sensor_entities[binary_sensor.intellifire_timer_on-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'attribution': 'Data provided by unpublished Intellifire API',
'friendly_name': 'IntelliFire Timer on',
}),
'context': <ANY>,
'entity_id': 'binary_sensor.intellifire_timer_on',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'off',
})
# ---

View File

@ -0,0 +1,66 @@
# serializer version: 1
# name: test_all_sensor_entities[climate.intellifire_thermostat-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': dict({
'hvac_modes': list([
<HVACMode.HEAT: 'heat'>,
<HVACMode.OFF: 'off'>,
]),
'max_temp': 37,
'min_temp': 0,
'target_temp_step': 1.0,
}),
'config_entry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'climate',
'entity_category': None,
'entity_id': 'climate.intellifire_thermostat',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'Thermostat',
'platform': 'intellifire',
'previous_unique_id': None,
'supported_features': <ClimateEntityFeature: 385>,
'translation_key': None,
'unique_id': 'climate_mock_serial',
'unit_of_measurement': None,
})
# ---
# name: test_all_sensor_entities[climate.intellifire_thermostat-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'attribution': 'Data provided by unpublished Intellifire API',
'current_temperature': 17.0,
'friendly_name': 'IntelliFire Thermostat',
'hvac_modes': list([
<HVACMode.HEAT: 'heat'>,
<HVACMode.OFF: 'off'>,
]),
'max_temp': 37,
'min_temp': 0,
'supported_features': <ClimateEntityFeature: 385>,
'target_temp_step': 1.0,
'temperature': 0.0,
}),
'context': <ANY>,
'entity_id': 'climate.intellifire_thermostat',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'off',
})
# ---

View File

@ -0,0 +1,587 @@
# serializer version: 1
# name: test_all_sensor_entities[sensor.intellifire_connection_quality-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'sensor',
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
'entity_id': 'sensor.intellifire_connection_quality',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'Connection quality',
'platform': 'intellifire',
'previous_unique_id': None,
'supported_features': 0,
'translation_key': 'connection_quality',
'unique_id': 'connection_quality_mock_serial',
'unit_of_measurement': None,
})
# ---
# name: test_all_sensor_entities[sensor.intellifire_connection_quality-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'attribution': 'Data provided by unpublished Intellifire API',
'friendly_name': 'IntelliFire Connection quality',
}),
'context': <ANY>,
'entity_id': 'sensor.intellifire_connection_quality',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': '988451',
})
# ---
# name: test_all_sensor_entities[sensor.intellifire_downtime-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'sensor',
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
'entity_id': 'sensor.intellifire_downtime',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': <SensorDeviceClass.TIMESTAMP: 'timestamp'>,
'original_icon': None,
'original_name': 'Downtime',
'platform': 'intellifire',
'previous_unique_id': None,
'supported_features': 0,
'translation_key': 'downtime',
'unique_id': 'downtime_mock_serial',
'unit_of_measurement': None,
})
# ---
# name: test_all_sensor_entities[sensor.intellifire_downtime-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'attribution': 'Data provided by unpublished Intellifire API',
'device_class': 'timestamp',
'friendly_name': 'IntelliFire Downtime',
}),
'context': <ANY>,
'entity_id': 'sensor.intellifire_downtime',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'unknown',
})
# ---
# name: test_all_sensor_entities[sensor.intellifire_ecm_latency-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'sensor',
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
'entity_id': 'sensor.intellifire_ecm_latency',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'ECM latency',
'platform': 'intellifire',
'previous_unique_id': None,
'supported_features': 0,
'translation_key': 'ecm_latency',
'unique_id': 'ecm_latency_mock_serial',
'unit_of_measurement': None,
})
# ---
# name: test_all_sensor_entities[sensor.intellifire_ecm_latency-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'attribution': 'Data provided by unpublished Intellifire API',
'friendly_name': 'IntelliFire ECM latency',
}),
'context': <ANY>,
'entity_id': 'sensor.intellifire_ecm_latency',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': '0',
})
# ---
# name: test_all_sensor_entities[sensor.intellifire_fan_speed-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': dict({
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
}),
'config_entry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'sensor',
'entity_category': None,
'entity_id': 'sensor.intellifire_fan_speed',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'Fan Speed',
'platform': 'intellifire',
'previous_unique_id': None,
'supported_features': 0,
'translation_key': 'fan_speed',
'unique_id': 'fan_speed_mock_serial',
'unit_of_measurement': None,
})
# ---
# name: test_all_sensor_entities[sensor.intellifire_fan_speed-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'attribution': 'Data provided by unpublished Intellifire API',
'friendly_name': 'IntelliFire Fan Speed',
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
}),
'context': <ANY>,
'entity_id': 'sensor.intellifire_fan_speed',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': '1',
})
# ---
# name: test_all_sensor_entities[sensor.intellifire_flame_height-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': dict({
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
}),
'config_entry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'sensor',
'entity_category': None,
'entity_id': 'sensor.intellifire_flame_height',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'Flame height',
'platform': 'intellifire',
'previous_unique_id': None,
'supported_features': 0,
'translation_key': 'flame_height',
'unique_id': 'flame_height_mock_serial',
'unit_of_measurement': None,
})
# ---
# name: test_all_sensor_entities[sensor.intellifire_flame_height-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'attribution': 'Data provided by unpublished Intellifire API',
'friendly_name': 'IntelliFire Flame height',
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
}),
'context': <ANY>,
'entity_id': 'sensor.intellifire_flame_height',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': '2',
})
# ---
# name: test_all_sensor_entities[sensor.intellifire_ip_address-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'sensor',
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
'entity_id': 'sensor.intellifire_ip_address',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'IP address',
'platform': 'intellifire',
'previous_unique_id': None,
'supported_features': 0,
'translation_key': 'ipv4_address',
'unique_id': 'ipv4_address_mock_serial',
'unit_of_measurement': None,
})
# ---
# name: test_all_sensor_entities[sensor.intellifire_ip_address-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'attribution': 'Data provided by unpublished Intellifire API',
'friendly_name': 'IntelliFire IP address',
}),
'context': <ANY>,
'entity_id': 'sensor.intellifire_ip_address',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': '192.168.2.108',
})
# ---
# name: test_all_sensor_entities[sensor.intellifire_local_connectivity-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'sensor',
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
'entity_id': 'sensor.intellifire_local_connectivity',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'Local connectivity',
'platform': 'intellifire',
'previous_unique_id': None,
'supported_features': 0,
'translation_key': 'local_connectivity',
'unique_id': 'local_connectivity_mock_serial',
'unit_of_measurement': None,
})
# ---
# name: test_all_sensor_entities[sensor.intellifire_local_connectivity-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'attribution': 'Data provided by unpublished Intellifire API',
'friendly_name': 'IntelliFire Local connectivity',
}),
'context': <ANY>,
'entity_id': 'sensor.intellifire_local_connectivity',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'True',
})
# ---
# name: test_all_sensor_entities[sensor.intellifire_none-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'sensor',
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
'entity_id': 'sensor.intellifire_none',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': None,
'platform': 'intellifire',
'previous_unique_id': None,
'supported_features': 0,
'translation_key': 'local_connectivity',
'unique_id': 'local_connectivity_mock_serial',
'unit_of_measurement': None,
})
# ---
# name: test_all_sensor_entities[sensor.intellifire_none-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'attribution': 'Data provided by unpublished Intellifire API',
'friendly_name': 'IntelliFire None',
}),
'context': <ANY>,
'entity_id': 'sensor.intellifire_none',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'True',
})
# ---
# name: test_all_sensor_entities[sensor.intellifire_target_temperature-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': dict({
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
}),
'config_entry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'sensor',
'entity_category': None,
'entity_id': 'sensor.intellifire_target_temperature',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': <SensorDeviceClass.TEMPERATURE: 'temperature'>,
'original_icon': None,
'original_name': 'Target temperature',
'platform': 'intellifire',
'previous_unique_id': None,
'supported_features': 0,
'translation_key': 'target_temp',
'unique_id': 'target_temp_mock_serial',
'unit_of_measurement': <UnitOfTemperature.CELSIUS: '°C'>,
})
# ---
# name: test_all_sensor_entities[sensor.intellifire_target_temperature-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'attribution': 'Data provided by unpublished Intellifire API',
'device_class': 'temperature',
'friendly_name': 'IntelliFire Target temperature',
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
'unit_of_measurement': <UnitOfTemperature.CELSIUS: '°C'>,
}),
'context': <ANY>,
'entity_id': 'sensor.intellifire_target_temperature',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': '0.0',
})
# ---
# name: test_all_sensor_entities[sensor.intellifire_temperature-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': dict({
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
}),
'config_entry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'sensor',
'entity_category': None,
'entity_id': 'sensor.intellifire_temperature',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': <SensorDeviceClass.TEMPERATURE: 'temperature'>,
'original_icon': None,
'original_name': 'Temperature',
'platform': 'intellifire',
'previous_unique_id': None,
'supported_features': 0,
'translation_key': None,
'unique_id': 'temperature_mock_serial',
'unit_of_measurement': <UnitOfTemperature.CELSIUS: '°C'>,
})
# ---
# name: test_all_sensor_entities[sensor.intellifire_temperature-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'attribution': 'Data provided by unpublished Intellifire API',
'device_class': 'temperature',
'friendly_name': 'IntelliFire Temperature',
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
'unit_of_measurement': <UnitOfTemperature.CELSIUS: '°C'>,
}),
'context': <ANY>,
'entity_id': 'sensor.intellifire_temperature',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': '17',
})
# ---
# name: test_all_sensor_entities[sensor.intellifire_timer_end-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': dict({
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
}),
'config_entry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'sensor',
'entity_category': None,
'entity_id': 'sensor.intellifire_timer_end',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': <SensorDeviceClass.TIMESTAMP: 'timestamp'>,
'original_icon': None,
'original_name': 'Timer end',
'platform': 'intellifire',
'previous_unique_id': None,
'supported_features': 0,
'translation_key': 'timer_end_timestamp',
'unique_id': 'timer_end_timestamp_mock_serial',
'unit_of_measurement': None,
})
# ---
# name: test_all_sensor_entities[sensor.intellifire_timer_end-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'attribution': 'Data provided by unpublished Intellifire API',
'device_class': 'timestamp',
'friendly_name': 'IntelliFire Timer end',
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
}),
'context': <ANY>,
'entity_id': 'sensor.intellifire_timer_end',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'unknown',
})
# ---
# name: test_all_sensor_entities[sensor.intellifire_uptime-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'sensor',
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
'entity_id': 'sensor.intellifire_uptime',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': <SensorDeviceClass.TIMESTAMP: 'timestamp'>,
'original_icon': None,
'original_name': 'Uptime',
'platform': 'intellifire',
'previous_unique_id': None,
'supported_features': 0,
'translation_key': 'uptime',
'unique_id': 'uptime_mock_serial',
'unit_of_measurement': None,
})
# ---
# name: test_all_sensor_entities[sensor.intellifire_uptime-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'attribution': 'Data provided by unpublished Intellifire API',
'device_class': 'timestamp',
'friendly_name': 'IntelliFire Uptime',
}),
'context': <ANY>,
'entity_id': 'sensor.intellifire_uptime',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': '2021-01-01T11:58:03+00:00',
})
# ---

View File

@ -0,0 +1,35 @@
"""Test IntelliFire Binary Sensors."""
from unittest.mock import AsyncMock, patch
import pytest
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
@pytest.mark.usefixtures("entity_registry_enabled_by_default")
async def test_all_binary_sensor_entities(
hass: HomeAssistant,
snapshot: SnapshotAssertion,
mock_config_entry_current: MockConfigEntry,
entity_registry: er.EntityRegistry,
mock_apis_single_fp: tuple[AsyncMock, AsyncMock, AsyncMock],
) -> None:
"""Test all entities."""
with (
patch(
"homeassistant.components.intellifire.PLATFORMS", [Platform.BINARY_SENSOR]
),
):
await setup_integration(hass, mock_config_entry_current)
await snapshot_platform(
hass, entity_registry, snapshot, mock_config_entry_current.entry_id
)

View File

@ -0,0 +1,34 @@
"""Test climate."""
from unittest.mock import patch
from freezegun import freeze_time
import pytest
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
@freeze_time("2021-01-01T12:00:00Z")
@pytest.mark.usefixtures("entity_registry_enabled_by_default")
async def test_all_sensor_entities(
hass: HomeAssistant,
snapshot: SnapshotAssertion,
mock_config_entry_current: MockConfigEntry,
entity_registry: er.EntityRegistry,
mock_fp,
) -> None:
"""Test all entities."""
with (
patch("homeassistant.components.intellifire.PLATFORMS", [Platform.CLIMATE]),
):
await setup_integration(hass, mock_config_entry_current)
await snapshot_platform(
hass, entity_registry, snapshot, mock_config_entry_current.entry_id
)

View File

@ -1,323 +1,168 @@
"""Test the IntelliFire config flow."""
from unittest.mock import AsyncMock, MagicMock, patch
from unittest.mock import AsyncMock
from intellifire4py.exceptions import LoginException
from intellifire4py.exceptions import LoginError
from homeassistant import config_entries
from homeassistant.components import dhcp
from homeassistant.components.intellifire.config_flow import MANUAL_ENTRY_STRING
from homeassistant.components.intellifire.const import CONF_USER_ID, DOMAIN
from homeassistant.const import CONF_API_KEY, CONF_HOST, CONF_PASSWORD, CONF_USERNAME
from homeassistant.components.intellifire.const import CONF_SERIAL, DOMAIN
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME
from homeassistant.core import HomeAssistant
from homeassistant.data_entry_flow import FlowResultType
from .conftest import mock_api_connection_error
from tests.common import MockConfigEntry
@patch.multiple(
"homeassistant.components.intellifire.config_flow.IntellifireAPICloud",
login=AsyncMock(),
get_user_id=MagicMock(return_value="intellifire"),
get_fireplace_api_key=MagicMock(return_value="key"),
)
async def test_no_discovery(
async def test_standard_config_with_single_fireplace(
hass: HomeAssistant,
mock_setup_entry: AsyncMock,
mock_intellifire_config_flow: MagicMock,
mock_apis_single_fp,
) -> None:
"""Test we should get the manual discovery form - because no discovered fireplaces."""
with patch(
"homeassistant.components.intellifire.config_flow.AsyncUDPFireplaceFinder.search_fireplace",
return_value=[],
):
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_USER}
)
assert result["type"] is FlowResultType.FORM
"""Test standard flow with a user who has only a single fireplace."""
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["step_id"] == "manual_device_entry"
assert result["step_id"] == "cloud_api"
result2 = await hass.config_entries.flow.async_configure(
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
{
CONF_HOST: "1.1.1.1",
},
{CONF_USERNAME: "donJulio", CONF_PASSWORD: "Tequila0FD00m"},
)
await hass.async_block_till_done()
assert result2["type"] is FlowResultType.FORM
assert result2["step_id"] == "api_config"
result3 = await hass.config_entries.flow.async_configure(
result["flow_id"],
{CONF_USERNAME: "test", CONF_PASSWORD: "AROONIE"},
)
await hass.async_block_till_done()
assert result3["type"] is FlowResultType.CREATE_ENTRY
assert result3["title"] == "Fireplace 12345"
assert result3["data"] == {
CONF_HOST: "1.1.1.1",
CONF_USERNAME: "test",
CONF_PASSWORD: "AROONIE",
CONF_API_KEY: "key",
CONF_USER_ID: "intellifire",
# For a single fireplace we just create it
assert result["type"] == FlowResultType.CREATE_ENTRY
assert result["data"] == {
"ip_address": "192.168.2.108",
"api_key": "B5C4DA27AAEF31D1FB21AFF9BFA6BCD2",
"serial": "3FB284769E4736F30C8973A7ED358123",
"auth_cookie": "B984F21A6378560019F8A1CDE41B6782",
"web_client_id": "FA2B1C3045601234D0AE17D72F8E975",
"user_id": "52C3F9E8B9D3AC99F8E4D12345678901FE9A2BC7D85F7654E28BF98BCD123456",
"username": "grumpypanda@china.cn",
"password": "you-stole-my-pandas",
}
assert len(mock_setup_entry.mock_calls) == 1
@patch.multiple(
"homeassistant.components.intellifire.config_flow.IntellifireAPICloud",
login=AsyncMock(side_effect=mock_api_connection_error()),
get_user_id=MagicMock(return_value="intellifire"),
get_fireplace_api_key=MagicMock(return_value="key"),
)
async def test_single_discovery(
async def test_standard_config_with_pre_configured_fireplace(
hass: HomeAssistant,
mock_setup_entry: AsyncMock,
mock_intellifire_config_flow: MagicMock,
mock_config_entry_current,
mock_apis_single_fp,
) -> None:
"""Test single fireplace UDP discovery."""
with patch(
"homeassistant.components.intellifire.config_flow.AsyncUDPFireplaceFinder.search_fireplace",
return_value=["192.168.1.69"],
):
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_USER}
)
"""What if we try to configure an already configured fireplace."""
# Configure an existing entry
mock_config_entry_current.add_to_hass(hass)
await hass.config_entries.flow.async_configure(
result["flow_id"], {CONF_HOST: "192.168.1.69"}
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_USER}
)
await hass.async_block_till_done()
result3 = await hass.config_entries.flow.async_configure(
assert result["type"] == FlowResultType.FORM
assert result["errors"] == {}
assert result["step_id"] == "cloud_api"
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
{CONF_USERNAME: "test", CONF_PASSWORD: "AROONIE"},
{CONF_USERNAME: "donJulio", CONF_PASSWORD: "Tequila0FD00m"},
)
await hass.async_block_till_done()
assert result3["type"] is FlowResultType.FORM
assert result3["errors"] == {"base": "iftapi_connect"}
# For a single fireplace we just create it
assert result["type"] == FlowResultType.ABORT
assert result["reason"] == "no_available_devices"
@patch.multiple(
"homeassistant.components.intellifire.config_flow.IntellifireAPICloud",
login=AsyncMock(side_effect=LoginException),
get_user_id=MagicMock(return_value="intellifire"),
get_fireplace_api_key=MagicMock(return_value="key"),
)
async def test_single_discovery_loign_error(
async def test_standard_config_with_single_fireplace_and_bad_credentials(
hass: HomeAssistant,
mock_setup_entry: AsyncMock,
mock_intellifire_config_flow: MagicMock,
mock_apis_single_fp,
) -> None:
"""Test single fireplace UDP discovery."""
with patch(
"homeassistant.components.intellifire.config_flow.AsyncUDPFireplaceFinder.search_fireplace",
return_value=["192.168.1.69"],
):
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_USER}
)
await hass.config_entries.flow.async_configure(
result["flow_id"], {CONF_HOST: "192.168.1.69"}
)
await hass.async_block_till_done()
result3 = await hass.config_entries.flow.async_configure(
result["flow_id"],
{CONF_USERNAME: "test", CONF_PASSWORD: "AROONIE"},
)
await hass.async_block_till_done()
assert result3["type"] is FlowResultType.FORM
assert result3["errors"] == {"base": "api_error"}
async def test_manual_entry(
hass: HomeAssistant,
mock_setup_entry: AsyncMock,
mock_intellifire_config_flow: MagicMock,
) -> None:
"""Test for multiple Fireplace discovery - involving a pick_device step."""
with patch(
"homeassistant.components.intellifire.config_flow.AsyncUDPFireplaceFinder.search_fireplace",
return_value=["192.168.1.69", "192.168.1.33", "192.168.169"],
):
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_USER}
)
assert result["step_id"] == "pick_device"
result2 = await hass.config_entries.flow.async_configure(
result["flow_id"], user_input={CONF_HOST: MANUAL_ENTRY_STRING}
)
await hass.async_block_till_done()
assert result2["step_id"] == "manual_device_entry"
async def test_multi_discovery(
hass: HomeAssistant,
mock_setup_entry: AsyncMock,
mock_intellifire_config_flow: MagicMock,
) -> None:
"""Test for multiple fireplace discovery - involving a pick_device step."""
with patch(
"homeassistant.components.intellifire.config_flow.AsyncUDPFireplaceFinder.search_fireplace",
return_value=["192.168.1.69", "192.168.1.33", "192.168.169"],
):
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_USER}
)
assert result["step_id"] == "pick_device"
await hass.config_entries.flow.async_configure(
result["flow_id"], user_input={CONF_HOST: "192.168.1.33"}
)
await hass.async_block_till_done()
assert result["step_id"] == "pick_device"
async def test_multi_discovery_cannot_connect(
hass: HomeAssistant,
mock_setup_entry: AsyncMock,
mock_intellifire_config_flow: MagicMock,
) -> None:
"""Test for multiple fireplace discovery - involving a pick_device step."""
with patch(
"homeassistant.components.intellifire.config_flow.AsyncUDPFireplaceFinder.search_fireplace",
return_value=["192.168.1.69", "192.168.1.33", "192.168.169"],
):
mock_intellifire_config_flow.poll.side_effect = ConnectionError
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_USER}
)
assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "pick_device"
result2 = await hass.config_entries.flow.async_configure(
result["flow_id"], user_input={CONF_HOST: "192.168.1.33"}
)
await hass.async_block_till_done()
assert result2["type"] is FlowResultType.FORM
assert result2["errors"] == {"base": "cannot_connect"}
async def test_form_cannot_connect_manual_entry(
hass: HomeAssistant,
mock_intellifire_config_flow: MagicMock,
mock_fireplace_finder_single: AsyncMock,
) -> None:
"""Test we handle cannot connect error."""
mock_intellifire_config_flow.poll.side_effect = ConnectionError
"""Test bad credentials on a login."""
mock_local_interface, mock_cloud_interface, mock_fp = mock_apis_single_fp
# Set login error
mock_cloud_interface.login_with_credentials.side_effect = LoginError
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_USER}
)
assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "manual_device_entry"
assert result["errors"] == {}
assert result["step_id"] == "cloud_api"
result2 = await hass.config_entries.flow.async_configure(
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
{
CONF_HOST: "1.1.1.1",
},
{CONF_USERNAME: "donJulio", CONF_PASSWORD: "Tequila0FD00m"},
)
assert result2["type"] is FlowResultType.FORM
assert result2["errors"] == {"base": "cannot_connect"}
# Erase the error
mock_cloud_interface.login_with_credentials.side_effect = None
assert result["type"] == FlowResultType.FORM
assert result["errors"] == {"base": "api_error"}
assert result["step_id"] == "cloud_api"
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
{CONF_USERNAME: "donJulio", CONF_PASSWORD: "Tequila0FD00m"},
)
# For a single fireplace we just create it
assert result["type"] == FlowResultType.CREATE_ENTRY
assert result["data"] == {
"ip_address": "192.168.2.108",
"api_key": "B5C4DA27AAEF31D1FB21AFF9BFA6BCD2",
"serial": "3FB284769E4736F30C8973A7ED358123",
"auth_cookie": "B984F21A6378560019F8A1CDE41B6782",
"web_client_id": "FA2B1C3045601234D0AE17D72F8E975",
"user_id": "52C3F9E8B9D3AC99F8E4D12345678901FE9A2BC7D85F7654E28BF98BCD123456",
"username": "grumpypanda@china.cn",
"password": "you-stole-my-pandas",
}
async def test_picker_already_discovered(
async def test_standard_config_with_multiple_fireplace(
hass: HomeAssistant,
mock_setup_entry: AsyncMock,
mock_intellifire_config_flow: MagicMock,
mock_apis_multifp,
) -> None:
"""Test single fireplace UDP discovery."""
entry = MockConfigEntry(
domain=DOMAIN,
data={
"host": "192.168.1.3",
},
title="Fireplace",
unique_id=44444,
)
entry.add_to_hass(hass)
with patch(
"homeassistant.components.intellifire.config_flow.AsyncUDPFireplaceFinder.search_fireplace",
return_value=["192.168.1.3"],
):
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_USER}
)
await hass.async_block_till_done()
result2 = await hass.config_entries.flow.async_configure(
result["flow_id"],
{
CONF_HOST: "192.168.1.4",
},
)
assert result2["type"] is FlowResultType.FORM
assert len(mock_setup_entry.mock_calls) == 0
@patch.multiple(
"homeassistant.components.intellifire.config_flow.IntellifireAPICloud",
login=AsyncMock(),
get_user_id=MagicMock(return_value="intellifire"),
get_fireplace_api_key=MagicMock(return_value="key"),
)
async def test_reauth_flow(
hass: HomeAssistant,
mock_setup_entry: AsyncMock,
mock_intellifire_config_flow: MagicMock,
) -> None:
"""Test the reauth flow."""
entry = MockConfigEntry(
domain=DOMAIN,
data={
"host": "192.168.1.3",
},
title="Fireplace 1234",
version=1,
unique_id="4444",
)
entry.add_to_hass(hass)
"""Test multi-fireplace user who must be very rich."""
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={
"source": "reauth",
"unique_id": entry.unique_id,
"entry_id": entry.entry_id,
},
DOMAIN, context={"source": config_entries.SOURCE_USER}
)
assert result["type"] == FlowResultType.FORM
assert result["errors"] == {}
assert result["step_id"] == "cloud_api"
assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "api_config"
result3 = await hass.config_entries.flow.async_configure(
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
{CONF_USERNAME: "test", CONF_PASSWORD: "AROONIE"},
{CONF_USERNAME: "donJulio", CONF_PASSWORD: "Tequila0FD00m"},
)
await hass.async_block_till_done()
assert result3["type"] is FlowResultType.ABORT
assert entry.data[CONF_PASSWORD] == "AROONIE"
assert entry.data[CONF_USERNAME] == "test"
# When we have multiple fireplaces we get to pick a serial
assert result["type"] == FlowResultType.FORM
assert result["step_id"] == "pick_cloud_device"
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
{CONF_SERIAL: "4GC295860E5837G40D9974B7FD459234"},
)
assert result["type"] == FlowResultType.CREATE_ENTRY
assert result["data"] == {
"ip_address": "192.168.2.109",
"api_key": "D4C5EB28BBFF41E1FB21AFF9BFA6CD34",
"serial": "4GC295860E5837G40D9974B7FD459234",
"auth_cookie": "B984F21A6378560019F8A1CDE41B6782",
"web_client_id": "FA2B1C3045601234D0AE17D72F8E975",
"user_id": "52C3F9E8B9D3AC99F8E4D12345678901FE9A2BC7D85F7654E28BF98BCD123456",
"username": "grumpypanda@china.cn",
"password": "you-stole-my-pandas",
}
async def test_dhcp_discovery_intellifire_device(
hass: HomeAssistant,
mock_setup_entry: AsyncMock,
mock_intellifire_config_flow: MagicMock,
mock_apis_multifp,
) -> None:
"""Test successful DHCP Discovery."""
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={"source": config_entries.SOURCE_DHCP},
@ -327,26 +172,26 @@ async def test_dhcp_discovery_intellifire_device(
hostname="zentrios-Test",
),
)
assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "dhcp_confirm"
result2 = await hass.config_entries.flow.async_configure(result["flow_id"])
assert result2["type"] is FlowResultType.FORM
assert result2["step_id"] == "dhcp_confirm"
result3 = await hass.config_entries.flow.async_configure(
result2["flow_id"], user_input={}
assert result["type"] == FlowResultType.FORM
assert result["step_id"] == "cloud_api"
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
{CONF_USERNAME: "donJulio", CONF_PASSWORD: "Tequila0FD00m"},
)
assert result3["title"] == "Fireplace 12345"
assert result3["data"] == {"host": "1.1.1.1"}
assert result["type"] == FlowResultType.CREATE_ENTRY
async def test_dhcp_discovery_non_intellifire_device(
hass: HomeAssistant,
mock_intellifire_config_flow: MagicMock,
mock_setup_entry: AsyncMock,
mock_apis_multifp,
) -> None:
"""Test failed DHCP Discovery."""
"""Test successful DHCP Discovery of a non intellifire device.."""
mock_intellifire_config_flow.poll.side_effect = ConnectionError
# Patch poll with an exception
mock_local_interface, mock_cloud_interface, mock_fp = mock_apis_multifp
mock_local_interface.poll.side_effect = ConnectionError
result = await hass.config_entries.flow.async_init(
DOMAIN,
@ -357,6 +202,28 @@ async def test_dhcp_discovery_non_intellifire_device(
hostname="zentrios-Evil",
),
)
assert result["type"] is FlowResultType.ABORT
assert result["type"] == FlowResultType.ABORT
assert result["reason"] == "not_intellifire_device"
# Test is finished - the DHCP scanner detected a hostname that "might" be an IntelliFire device, but it was not.
async def test_reauth_flow(
hass: HomeAssistant,
mock_config_entry_current: MockConfigEntry,
mock_apis_single_fp,
mock_setup_entry: AsyncMock,
) -> None:
"""Test reauth."""
mock_config_entry_current.add_to_hass(hass)
result = await mock_config_entry_current.start_reauth_flow(hass)
assert result["type"] == FlowResultType.FORM
result["step_id"] = "cloud_api"
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
{CONF_USERNAME: "donJulio", CONF_PASSWORD: "Tequila0FD00m"},
)
assert result["type"] == FlowResultType.ABORT
assert result["reason"] == "reauth_successful"

View File

@ -0,0 +1,111 @@
"""Test the IntelliFire config flow."""
from unittest.mock import AsyncMock, patch
from homeassistant.components.intellifire import CONF_USER_ID
from homeassistant.components.intellifire.const import (
API_MODE_CLOUD,
API_MODE_LOCAL,
CONF_AUTH_COOKIE,
CONF_CONTROL_MODE,
CONF_READ_MODE,
CONF_SERIAL,
CONF_WEB_CLIENT_ID,
DOMAIN,
)
from homeassistant.config_entries import ConfigEntryState
from homeassistant.const import (
CONF_API_KEY,
CONF_HOST,
CONF_IP_ADDRESS,
CONF_PASSWORD,
CONF_USERNAME,
)
from homeassistant.core import HomeAssistant
from tests.common import MockConfigEntry
async def test_minor_migration(
hass: HomeAssistant, mock_config_entry_old, mock_apis_single_fp
) -> None:
"""With the new library we are going to end up rewriting the config entries."""
mock_config_entry_old.add_to_hass(hass)
await hass.config_entries.async_setup(mock_config_entry_old.entry_id)
assert mock_config_entry_old.data == {
"ip_address": "192.168.2.108",
"host": "192.168.2.108",
"api_key": "B5C4DA27AAEF31D1FB21AFF9BFA6BCD2",
"serial": "3FB284769E4736F30C8973A7ED358123",
"auth_cookie": "B984F21A6378560019F8A1CDE41B6782",
"web_client_id": "FA2B1C3045601234D0AE17D72F8E975",
"user_id": "52C3F9E8B9D3AC99F8E4D12345678901FE9A2BC7D85F7654E28BF98BCD123456",
"username": "grumpypanda@china.cn",
"password": "you-stole-my-pandas",
}
async def test_minor_migration_error(hass: HomeAssistant, mock_apis_single_fp) -> None:
"""Test the case where we completely fail to initialize."""
mock_config_entry = MockConfigEntry(
domain=DOMAIN,
version=1,
minor_version=1,
title="Fireplace of testing",
data={
CONF_HOST: "11.168.2.218",
CONF_USERNAME: "grumpypanda@china.cn",
CONF_PASSWORD: "you-stole-my-pandas",
CONF_USER_ID: "52C3F9E8B9D3AC99F8E4D12345678901FE9A2BC7D85F7654E28BF98BCD123456",
},
)
mock_config_entry.add_to_hass(hass)
await hass.config_entries.async_setup(mock_config_entry.entry_id)
await hass.async_block_till_done()
assert mock_config_entry.state is ConfigEntryState.MIGRATION_ERROR
async def test_init_with_no_username(hass: HomeAssistant, mock_apis_single_fp) -> None:
"""Test the case where we completely fail to initialize."""
mock_config_entry = MockConfigEntry(
domain=DOMAIN,
version=1,
minor_version=2,
data={
CONF_IP_ADDRESS: "192.168.2.108",
CONF_PASSWORD: "you-stole-my-pandas",
CONF_SERIAL: "3FB284769E4736F30C8973A7ED358123",
CONF_WEB_CLIENT_ID: "FA2B1C3045601234D0AE17D72F8E975",
CONF_API_KEY: "B5C4DA27AAEF31D1FB21AFF9BFA6BCD2",
CONF_AUTH_COOKIE: "B984F21A6378560019F8A1CDE41B6782",
CONF_USER_ID: "52C3F9E8B9D3AC99F8E4D12345678901FE9A2BC7D85F7654E28BF98BCD123456",
},
options={CONF_READ_MODE: API_MODE_LOCAL, CONF_CONTROL_MODE: API_MODE_CLOUD},
unique_id="3FB284769E4736F30C8973A7ED358123",
)
mock_config_entry.add_to_hass(hass)
await hass.config_entries.async_setup(mock_config_entry.entry_id)
await hass.async_block_till_done()
assert mock_config_entry.state is ConfigEntryState.SETUP_ERROR
async def test_connectivity_bad(
hass: HomeAssistant,
mock_config_entry_current,
mock_apis_single_fp,
) -> None:
"""Test a timeout error on the setup flow."""
with patch(
"homeassistant.components.intellifire.UnifiedFireplace.build_fireplace_from_common",
new_callable=AsyncMock,
side_effect=TimeoutError,
):
mock_config_entry_current.add_to_hass(hass)
await hass.config_entries.async_setup(mock_config_entry_current.entry_id)
await hass.async_block_till_done()
assert len(hass.states.async_all()) == 0

View File

@ -0,0 +1,35 @@
"""Test IntelliFire Binary Sensors."""
from unittest.mock import AsyncMock, patch
from freezegun import freeze_time
import pytest
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
@freeze_time("2021-01-01T12:00:00Z")
@pytest.mark.usefixtures("entity_registry_enabled_by_default")
async def test_all_sensor_entities(
hass: HomeAssistant,
snapshot: SnapshotAssertion,
mock_config_entry_current: MockConfigEntry,
entity_registry: er.EntityRegistry,
mock_apis_single_fp: tuple[AsyncMock, AsyncMock, AsyncMock],
) -> None:
"""Test all entities."""
with (
patch("homeassistant.components.intellifire.PLATFORMS", [Platform.SENSOR]),
):
await setup_integration(hass, mock_config_entry_current)
await snapshot_platform(
hass, entity_registry, snapshot, mock_config_entry_current.entry_id
)