Bump brother backend library (#78072)

* Update integration for a new library

* Update tests

* Add unique_id migration

* Update integration and tests for lib 2.0.0

* Improve test coverage

* Improve typing in tests
This commit is contained in:
Maciej Bieniek 2022-09-18 23:28:17 +02:00 committed by GitHub
parent 3c6c673a20
commit 721fddc016
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 182 additions and 62 deletions

View File

@ -5,12 +5,12 @@ from datetime import timedelta
import logging import logging
import async_timeout import async_timeout
from brother import Brother, DictToObj, SnmpError, UnsupportedModel from brother import Brother, BrotherSensors, SnmpError, UnsupportedModel
import pysnmp.hlapi.asyncio as SnmpEngine
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_HOST, CONF_TYPE, Platform from homeassistant.const import CONF_HOST, CONF_TYPE, Platform
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryNotReady
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
from .const import DATA_CONFIG_ENTRY, DOMAIN, SNMP from .const import DATA_CONFIG_ENTRY, DOMAIN, SNMP
@ -26,13 +26,17 @@ _LOGGER = logging.getLogger(__name__)
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Set up Brother from a config entry.""" """Set up Brother from a config entry."""
host = entry.data[CONF_HOST] host = entry.data[CONF_HOST]
kind = entry.data[CONF_TYPE] printer_type = entry.data[CONF_TYPE]
snmp_engine = get_snmp_engine(hass) snmp_engine = get_snmp_engine(hass)
try:
coordinator = BrotherDataUpdateCoordinator( brother = await Brother.create(
hass, host=host, kind=kind, snmp_engine=snmp_engine host, printer_type=printer_type, snmp_engine=snmp_engine
) )
except (ConnectionError, SnmpError) as error:
raise ConfigEntryNotReady from error
coordinator = BrotherDataUpdateCoordinator(hass, brother)
await coordinator.async_config_entry_first_refresh() await coordinator.async_config_entry_first_refresh()
hass.data.setdefault(DOMAIN, {}) hass.data.setdefault(DOMAIN, {})
@ -61,11 +65,9 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
class BrotherDataUpdateCoordinator(DataUpdateCoordinator): class BrotherDataUpdateCoordinator(DataUpdateCoordinator):
"""Class to manage fetching Brother data from the printer.""" """Class to manage fetching Brother data from the printer."""
def __init__( def __init__(self, hass: HomeAssistant, brother: Brother) -> None:
self, hass: HomeAssistant, host: str, kind: str, snmp_engine: SnmpEngine
) -> None:
"""Initialize.""" """Initialize."""
self.brother = Brother(host, kind=kind, snmp_engine=snmp_engine) self.brother = brother
super().__init__( super().__init__(
hass, hass,
@ -74,7 +76,7 @@ class BrotherDataUpdateCoordinator(DataUpdateCoordinator):
update_interval=SCAN_INTERVAL, update_interval=SCAN_INTERVAL,
) )
async def _async_update_data(self) -> DictToObj: async def _async_update_data(self) -> BrotherSensors:
"""Update data via library.""" """Update data via library."""
try: try:
async with async_timeout.timeout(20): async with async_timeout.timeout(20):

View File

@ -46,7 +46,9 @@ class BrotherConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
snmp_engine = get_snmp_engine(self.hass) snmp_engine = get_snmp_engine(self.hass)
brother = Brother(user_input[CONF_HOST], snmp_engine=snmp_engine) brother = await Brother.create(
user_input[CONF_HOST], snmp_engine=snmp_engine
)
await brother.async_update() await brother.async_update()
await self.async_set_unique_id(brother.serial.lower()) await self.async_set_unique_id(brother.serial.lower())
@ -80,7 +82,9 @@ class BrotherConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
model = discovery_info.properties.get("product") model = discovery_info.properties.get("product")
try: try:
self.brother = Brother(self.host, snmp_engine=snmp_engine, model=model) self.brother = await Brother.create(
self.host, snmp_engine=snmp_engine, model=model
)
await self.brother.async_update() await self.brother.async_update()
except UnsupportedModel: except UnsupportedModel:
return self.async_abort(reason="unsupported_model") return self.async_abort(reason="unsupported_model")

View File

@ -1,6 +1,8 @@
"""Diagnostics support for Brother.""" """Diagnostics support for Brother."""
from __future__ import annotations from __future__ import annotations
from dataclasses import asdict
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
@ -18,7 +20,7 @@ async def async_get_config_entry_diagnostics(
diagnostics_data = { diagnostics_data = {
"info": dict(config_entry.data), "info": dict(config_entry.data),
"data": coordinator.data, "data": asdict(coordinator.data),
} }
return diagnostics_data return diagnostics_data

View File

@ -3,7 +3,7 @@
"name": "Brother Printer", "name": "Brother Printer",
"documentation": "https://www.home-assistant.io/integrations/brother", "documentation": "https://www.home-assistant.io/integrations/brother",
"codeowners": ["@bieniu"], "codeowners": ["@bieniu"],
"requirements": ["brother==1.2.3"], "requirements": ["brother==2.0.0"],
"zeroconf": [ "zeroconf": [
{ {
"type": "_printer._tcp.local.", "type": "_printer._tcp.local.",

View File

@ -3,9 +3,11 @@ from __future__ import annotations
from dataclasses import dataclass from dataclasses import dataclass
from datetime import datetime from datetime import datetime
import logging
from typing import Any, cast from typing import Any, cast
from homeassistant.components.sensor import ( from homeassistant.components.sensor import (
DOMAIN as PLATFORM,
SensorDeviceClass, SensorDeviceClass,
SensorEntity, SensorEntity,
SensorEntityDescription, SensorEntityDescription,
@ -14,6 +16,7 @@ from homeassistant.components.sensor import (
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_HOST, PERCENTAGE from homeassistant.const import CONF_HOST, PERCENTAGE
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_registry as er
from homeassistant.helpers.entity import DeviceInfo, EntityCategory from homeassistant.helpers.entity import DeviceInfo, EntityCategory
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import StateType from homeassistant.helpers.typing import StateType
@ -28,7 +31,7 @@ ATTR_BLACK_DRUM_REMAINING_LIFE = "black_drum_remaining_life"
ATTR_BLACK_DRUM_REMAINING_PAGES = "black_drum_remaining_pages" ATTR_BLACK_DRUM_REMAINING_PAGES = "black_drum_remaining_pages"
ATTR_BLACK_INK_REMAINING = "black_ink_remaining" ATTR_BLACK_INK_REMAINING = "black_ink_remaining"
ATTR_BLACK_TONER_REMAINING = "black_toner_remaining" ATTR_BLACK_TONER_REMAINING = "black_toner_remaining"
ATTR_BW_COUNTER = "b/w_counter" ATTR_BW_COUNTER = "bw_counter"
ATTR_COLOR_COUNTER = "color_counter" ATTR_COLOR_COUNTER = "color_counter"
ATTR_COUNTER = "counter" ATTR_COUNTER = "counter"
ATTR_CYAN_DRUM_COUNTER = "cyan_drum_counter" ATTR_CYAN_DRUM_COUNTER = "cyan_drum_counter"
@ -82,6 +85,8 @@ ATTRS_MAP: dict[str, tuple[str, str]] = {
), ),
} }
_LOGGER = logging.getLogger(__name__)
async def async_setup_entry( async def async_setup_entry(
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
@ -89,6 +94,22 @@ async def async_setup_entry(
"""Add Brother entities from a config_entry.""" """Add Brother entities from a config_entry."""
coordinator = hass.data[DOMAIN][DATA_CONFIG_ENTRY][entry.entry_id] coordinator = hass.data[DOMAIN][DATA_CONFIG_ENTRY][entry.entry_id]
# Due to the change of the attribute name of one sensor, it is necessary to migrate
# the unique_id to the new one.
entity_registry = er.async_get(hass)
old_unique_id = f"{coordinator.data.serial.lower()}_b/w_counter"
if entity_id := entity_registry.async_get_entity_id(
PLATFORM, DOMAIN, old_unique_id
):
new_unique_id = f"{coordinator.data.serial.lower()}_bw_counter"
_LOGGER.debug(
"Migrating entity %s from old unique ID '%s' to new unique ID '%s'",
entity_id,
old_unique_id,
new_unique_id,
)
entity_registry.async_update_entity(entity_id, new_unique_id=new_unique_id)
sensors = [] sensors = []
device_info = DeviceInfo( device_info = DeviceInfo(
@ -97,11 +118,11 @@ async def async_setup_entry(
manufacturer=ATTR_MANUFACTURER, manufacturer=ATTR_MANUFACTURER,
model=coordinator.data.model, model=coordinator.data.model,
name=coordinator.data.model, name=coordinator.data.model,
sw_version=getattr(coordinator.data, "firmware", None), sw_version=coordinator.data.firmware,
) )
for description in SENSOR_TYPES: for description in SENSOR_TYPES:
if description.key in coordinator.data: if getattr(coordinator.data, description.key) is not None:
sensors.append( sensors.append(
description.entity_class(coordinator, description, device_info) description.entity_class(coordinator, description, device_info)
) )

View File

@ -449,7 +449,7 @@ boto3==1.20.24
broadlink==0.18.2 broadlink==0.18.2
# homeassistant.components.brother # homeassistant.components.brother
brother==1.2.3 brother==2.0.0
# homeassistant.components.brottsplatskartan # homeassistant.components.brottsplatskartan
brottsplatskartan==0.0.1 brottsplatskartan==0.0.1

View File

@ -356,7 +356,7 @@ boschshcpy==0.2.30
broadlink==0.18.2 broadlink==0.18.2
# homeassistant.components.brother # homeassistant.components.brother
brother==1.2.3 brother==2.0.0
# homeassistant.components.brunt # homeassistant.components.brunt
brunt==1.2.0 brunt==1.2.0

View File

@ -4,11 +4,14 @@ from unittest.mock import patch
from homeassistant.components.brother.const import DOMAIN from homeassistant.components.brother.const import DOMAIN
from homeassistant.const import CONF_HOST, CONF_TYPE from homeassistant.const import CONF_HOST, CONF_TYPE
from homeassistant.core import HomeAssistant
from tests.common import MockConfigEntry, load_fixture from tests.common import MockConfigEntry, load_fixture
async def init_integration(hass, skip_setup=False) -> MockConfigEntry: async def init_integration(
hass: HomeAssistant, skip_setup: bool = False
) -> MockConfigEntry:
"""Set up the Brother integration in Home Assistant.""" """Set up the Brother integration in Home Assistant."""
entry = MockConfigEntry( entry = MockConfigEntry(
domain=DOMAIN, domain=DOMAIN,
@ -20,7 +23,7 @@ async def init_integration(hass, skip_setup=False) -> MockConfigEntry:
entry.add_to_hass(hass) entry.add_to_hass(hass)
if not skip_setup: if not skip_setup:
with patch( with patch("brother.Brother.initialize"), patch(
"brother.Brother._get_data", "brother.Brother._get_data",
return_value=json.loads(load_fixture("printer_data.json", "brother")), return_value=json.loads(load_fixture("printer_data.json", "brother")),
): ):

View File

@ -1,5 +1,10 @@
{ {
"b/w_counter": 709, "black_counter": null,
"black_ink": null,
"black_ink_remaining": null,
"black_ink_status": null,
"cyan_counter": null,
"bw_counter": 709,
"belt_unit_remaining_life": 97, "belt_unit_remaining_life": 97,
"belt_unit_remaining_pages": 48436, "belt_unit_remaining_pages": 48436,
"black_drum_counter": 1611, "black_drum_counter": 1611,
@ -12,6 +17,9 @@
"cyan_drum_counter": 1611, "cyan_drum_counter": 1611,
"cyan_drum_remaining_life": 92, "cyan_drum_remaining_life": 92,
"cyan_drum_remaining_pages": 16389, "cyan_drum_remaining_pages": 16389,
"cyan_ink": null,
"cyan_ink_remaining": null,
"cyan_ink_status": null,
"cyan_toner": 10, "cyan_toner": 10,
"cyan_toner_remaining": 10, "cyan_toner_remaining": 10,
"cyan_toner_status": 1, "cyan_toner_status": 1,
@ -22,10 +30,17 @@
"duplex_unit_pages_counter": 538, "duplex_unit_pages_counter": 538,
"firmware": "1.17", "firmware": "1.17",
"fuser_remaining_life": 97, "fuser_remaining_life": 97,
"fuser_unit_remaining_pages": null,
"image_counter": null,
"laser_remaining_life": null,
"laser_unit_remaining_pages": 48389, "laser_unit_remaining_pages": 48389,
"magenta_counter": null,
"magenta_drum_counter": 1611, "magenta_drum_counter": 1611,
"magenta_drum_remaining_life": 92, "magenta_drum_remaining_life": 92,
"magenta_drum_remaining_pages": 16389, "magenta_drum_remaining_pages": 16389,
"magenta_ink": null,
"magenta_ink_remaining": null,
"magenta_ink_status": null,
"magenta_toner": 10, "magenta_toner": 10,
"magenta_toner_remaining": 8, "magenta_toner_remaining": 8,
"magenta_toner_status": 2, "magenta_toner_status": 2,
@ -33,12 +48,18 @@
"page_counter": 986, "page_counter": 986,
"pf_kit_1_remaining_life": 98, "pf_kit_1_remaining_life": 98,
"pf_kit_1_remaining_pages": 48741, "pf_kit_1_remaining_pages": 48741,
"pf_kit_mp_remaining_life": null,
"pf_kit_mp_remaining_pages": null,
"serial": "0123456789", "serial": "0123456789",
"status": "waiting", "status": "waiting",
"uptime": "2019-09-24T12:14:56+00:00", "uptime": "2019-09-24T12:14:56+00:00",
"yellow_counter": null,
"yellow_drum_counter": 1611, "yellow_drum_counter": 1611,
"yellow_drum_remaining_life": 92, "yellow_drum_remaining_life": 92,
"yellow_drum_remaining_pages": 16389, "yellow_drum_remaining_pages": 16389,
"yellow_ink": null,
"yellow_ink_remaining": null,
"yellow_ink_status": null,
"yellow_toner": 10, "yellow_toner": 10,
"yellow_toner_remaining": 2, "yellow_toner_remaining": 2,
"yellow_toner_status": 2 "yellow_toner_status": 2

View File

@ -9,13 +9,14 @@ from homeassistant.components import zeroconf
from homeassistant.components.brother.const import DOMAIN from homeassistant.components.brother.const import DOMAIN
from homeassistant.config_entries import SOURCE_USER, SOURCE_ZEROCONF from homeassistant.config_entries import SOURCE_USER, SOURCE_ZEROCONF
from homeassistant.const import CONF_HOST, CONF_TYPE from homeassistant.const import CONF_HOST, CONF_TYPE
from homeassistant.core import HomeAssistant
from tests.common import MockConfigEntry, load_fixture from tests.common import MockConfigEntry, load_fixture
CONFIG = {CONF_HOST: "127.0.0.1", CONF_TYPE: "laser"} CONFIG = {CONF_HOST: "127.0.0.1", CONF_TYPE: "laser"}
async def test_show_form(hass): async def test_show_form(hass: HomeAssistant) -> None:
"""Test that the form is served with no input.""" """Test that the form is served with no input."""
result = await hass.config_entries.flow.async_init( result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": SOURCE_USER} DOMAIN, context={"source": SOURCE_USER}
@ -25,9 +26,9 @@ async def test_show_form(hass):
assert result["step_id"] == SOURCE_USER assert result["step_id"] == SOURCE_USER
async def test_create_entry_with_hostname(hass): async def test_create_entry_with_hostname(hass: HomeAssistant) -> None:
"""Test that the user step works with printer hostname.""" """Test that the user step works with printer hostname."""
with patch( with patch("brother.Brother.initialize"), patch(
"brother.Brother._get_data", "brother.Brother._get_data",
return_value=json.loads(load_fixture("printer_data.json", "brother")), return_value=json.loads(load_fixture("printer_data.json", "brother")),
): ):
@ -43,9 +44,9 @@ async def test_create_entry_with_hostname(hass):
assert result["data"][CONF_TYPE] == "laser" assert result["data"][CONF_TYPE] == "laser"
async def test_create_entry_with_ipv4_address(hass): async def test_create_entry_with_ipv4_address(hass: HomeAssistant) -> None:
"""Test that the user step works with printer IPv4 address.""" """Test that the user step works with printer IPv4 address."""
with patch( with patch("brother.Brother.initialize"), patch(
"brother.Brother._get_data", "brother.Brother._get_data",
return_value=json.loads(load_fixture("printer_data.json", "brother")), return_value=json.loads(load_fixture("printer_data.json", "brother")),
): ):
@ -59,9 +60,9 @@ async def test_create_entry_with_ipv4_address(hass):
assert result["data"][CONF_TYPE] == "laser" assert result["data"][CONF_TYPE] == "laser"
async def test_create_entry_with_ipv6_address(hass): async def test_create_entry_with_ipv6_address(hass: HomeAssistant) -> None:
"""Test that the user step works with printer IPv6 address.""" """Test that the user step works with printer IPv6 address."""
with patch( with patch("brother.Brother.initialize"), patch(
"brother.Brother._get_data", "brother.Brother._get_data",
return_value=json.loads(load_fixture("printer_data.json", "brother")), return_value=json.loads(load_fixture("printer_data.json", "brother")),
): ):
@ -77,7 +78,7 @@ async def test_create_entry_with_ipv6_address(hass):
assert result["data"][CONF_TYPE] == "laser" assert result["data"][CONF_TYPE] == "laser"
async def test_invalid_hostname(hass): async def test_invalid_hostname(hass: HomeAssistant) -> None:
"""Test invalid hostname in user_input.""" """Test invalid hostname in user_input."""
result = await hass.config_entries.flow.async_init( result = await hass.config_entries.flow.async_init(
DOMAIN, DOMAIN,
@ -88,9 +89,11 @@ async def test_invalid_hostname(hass):
assert result["errors"] == {CONF_HOST: "wrong_host"} assert result["errors"] == {CONF_HOST: "wrong_host"}
async def test_connection_error(hass): async def test_connection_error(hass: HomeAssistant) -> None:
"""Test connection to host error.""" """Test connection to host error."""
with patch("brother.Brother._get_data", side_effect=ConnectionError()): with patch("brother.Brother.initialize"), patch(
"brother.Brother._get_data", side_effect=ConnectionError()
):
result = await hass.config_entries.flow.async_init( result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": SOURCE_USER}, data=CONFIG DOMAIN, context={"source": SOURCE_USER}, data=CONFIG
) )
@ -98,9 +101,11 @@ async def test_connection_error(hass):
assert result["errors"] == {"base": "cannot_connect"} assert result["errors"] == {"base": "cannot_connect"}
async def test_snmp_error(hass): async def test_snmp_error(hass: HomeAssistant) -> None:
"""Test SNMP error.""" """Test SNMP error."""
with patch("brother.Brother._get_data", side_effect=SnmpError("error")): with patch("brother.Brother.initialize"), patch(
"brother.Brother._get_data", side_effect=SnmpError("error")
):
result = await hass.config_entries.flow.async_init( result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": SOURCE_USER}, data=CONFIG DOMAIN, context={"source": SOURCE_USER}, data=CONFIG
) )
@ -108,9 +113,11 @@ async def test_snmp_error(hass):
assert result["errors"] == {"base": "snmp_error"} assert result["errors"] == {"base": "snmp_error"}
async def test_unsupported_model_error(hass): async def test_unsupported_model_error(hass: HomeAssistant) -> None:
"""Test unsupported printer model error.""" """Test unsupported printer model error."""
with patch("brother.Brother._get_data", side_effect=UnsupportedModel("error")): with patch("brother.Brother.initialize"), patch(
"brother.Brother._get_data", side_effect=UnsupportedModel("error")
):
result = await hass.config_entries.flow.async_init( result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": SOURCE_USER}, data=CONFIG DOMAIN, context={"source": SOURCE_USER}, data=CONFIG
@ -120,9 +127,9 @@ async def test_unsupported_model_error(hass):
assert result["reason"] == "unsupported_model" assert result["reason"] == "unsupported_model"
async def test_device_exists_abort(hass): async def test_device_exists_abort(hass: HomeAssistant) -> None:
"""Test we abort config flow if Brother printer already configured.""" """Test we abort config flow if Brother printer already configured."""
with patch( with patch("brother.Brother.initialize"), patch(
"brother.Brother._get_data", "brother.Brother._get_data",
return_value=json.loads(load_fixture("printer_data.json", "brother")), return_value=json.loads(load_fixture("printer_data.json", "brother")),
): ):
@ -137,9 +144,11 @@ async def test_device_exists_abort(hass):
assert result["reason"] == "already_configured" assert result["reason"] == "already_configured"
async def test_zeroconf_snmp_error(hass): async def test_zeroconf_snmp_error(hass: HomeAssistant) -> None:
"""Test we abort zeroconf flow on SNMP error.""" """Test we abort zeroconf flow on SNMP error."""
with patch("brother.Brother._get_data", side_effect=SnmpError("error")): with patch("brother.Brother.initialize"), patch(
"brother.Brother._get_data", side_effect=SnmpError("error")
):
result = await hass.config_entries.flow.async_init( result = await hass.config_entries.flow.async_init(
DOMAIN, DOMAIN,
@ -159,9 +168,11 @@ async def test_zeroconf_snmp_error(hass):
assert result["reason"] == "cannot_connect" assert result["reason"] == "cannot_connect"
async def test_zeroconf_unsupported_model(hass): async def test_zeroconf_unsupported_model(hass: HomeAssistant) -> None:
"""Test unsupported printer model error.""" """Test unsupported printer model error."""
with patch("brother.Brother._get_data") as mock_get_data: with patch("brother.Brother.initialize"), patch(
"brother.Brother._get_data"
) as mock_get_data:
result = await hass.config_entries.flow.async_init( result = await hass.config_entries.flow.async_init(
DOMAIN, DOMAIN,
context={"source": SOURCE_ZEROCONF}, context={"source": SOURCE_ZEROCONF},
@ -181,9 +192,9 @@ async def test_zeroconf_unsupported_model(hass):
assert len(mock_get_data.mock_calls) == 0 assert len(mock_get_data.mock_calls) == 0
async def test_zeroconf_device_exists_abort(hass): async def test_zeroconf_device_exists_abort(hass: HomeAssistant) -> None:
"""Test we abort zeroconf flow if Brother printer already configured.""" """Test we abort zeroconf flow if Brother printer already configured."""
with patch( with patch("brother.Brother.initialize"), patch(
"brother.Brother._get_data", "brother.Brother._get_data",
return_value=json.loads(load_fixture("printer_data.json", "brother")), return_value=json.loads(load_fixture("printer_data.json", "brother")),
): ):
@ -215,11 +226,13 @@ async def test_zeroconf_device_exists_abort(hass):
assert entry.data["host"] == "127.0.0.1" assert entry.data["host"] == "127.0.0.1"
async def test_zeroconf_no_probe_existing_device(hass): async def test_zeroconf_no_probe_existing_device(hass: HomeAssistant) -> None:
"""Test we do not probe the device is the host is already configured.""" """Test we do not probe the device is the host is already configured."""
entry = MockConfigEntry(domain=DOMAIN, unique_id="0123456789", data=CONFIG) entry = MockConfigEntry(domain=DOMAIN, unique_id="0123456789", data=CONFIG)
entry.add_to_hass(hass) entry.add_to_hass(hass)
with patch("brother.Brother._get_data") as mock_get_data: with patch("brother.Brother.initialize"), patch(
"brother.Brother._get_data"
) as mock_get_data:
result = await hass.config_entries.flow.async_init( result = await hass.config_entries.flow.async_init(
DOMAIN, DOMAIN,
context={"source": SOURCE_ZEROCONF}, context={"source": SOURCE_ZEROCONF},
@ -240,9 +253,9 @@ async def test_zeroconf_no_probe_existing_device(hass):
assert len(mock_get_data.mock_calls) == 0 assert len(mock_get_data.mock_calls) == 0
async def test_zeroconf_confirm_create_entry(hass): async def test_zeroconf_confirm_create_entry(hass: HomeAssistant) -> None:
"""Test zeroconf confirmation and create config entry.""" """Test zeroconf confirmation and create config entry."""
with patch( with patch("brother.Brother.initialize"), patch(
"brother.Brother._get_data", "brother.Brother._get_data",
return_value=json.loads(load_fixture("printer_data.json", "brother")), return_value=json.loads(load_fixture("printer_data.json", "brother")),
): ):

View File

@ -1,8 +1,12 @@
"""Test Brother diagnostics.""" """Test Brother diagnostics."""
from collections.abc import Awaitable, Callable
from datetime import datetime from datetime import datetime
import json import json
from unittest.mock import Mock, patch from unittest.mock import Mock, patch
from aiohttp import ClientSession
from homeassistant.core import HomeAssistant
from homeassistant.util.dt import UTC from homeassistant.util.dt import UTC
from tests.common import load_fixture from tests.common import load_fixture
@ -10,13 +14,17 @@ from tests.components.brother import init_integration
from tests.components.diagnostics import get_diagnostics_for_config_entry from tests.components.diagnostics import get_diagnostics_for_config_entry
async def test_entry_diagnostics(hass, hass_client): async def test_entry_diagnostics(
hass: HomeAssistant, hass_client: Callable[..., Awaitable[ClientSession]]
) -> None:
"""Test config entry diagnostics.""" """Test config entry diagnostics."""
entry = await init_integration(hass, skip_setup=True) entry = await init_integration(hass, skip_setup=True)
diagnostics_data = json.loads(load_fixture("diagnostics_data.json", "brother")) diagnostics_data = json.loads(load_fixture("diagnostics_data.json", "brother"))
test_time = datetime(2019, 11, 11, 9, 10, 32, tzinfo=UTC) test_time = datetime(2019, 11, 11, 9, 10, 32, tzinfo=UTC)
with patch("brother.datetime", utcnow=Mock(return_value=test_time)), patch( with patch("brother.Brother.initialize"), patch(
"brother.datetime", utcnow=Mock(return_value=test_time)
), patch(
"brother.Brother._get_data", "brother.Brother._get_data",
return_value=json.loads(load_fixture("printer_data.json", "brother")), return_value=json.loads(load_fixture("printer_data.json", "brother")),
): ):

View File

@ -1,15 +1,19 @@
"""Test init of Brother integration.""" """Test init of Brother integration."""
from unittest.mock import patch from unittest.mock import patch
from brother import SnmpError
import pytest
from homeassistant.components.brother.const import DOMAIN from homeassistant.components.brother.const import DOMAIN
from homeassistant.config_entries import ConfigEntryState from homeassistant.config_entries import ConfigEntryState
from homeassistant.const import CONF_HOST, CONF_TYPE, STATE_UNAVAILABLE from homeassistant.const import CONF_HOST, CONF_TYPE, STATE_UNAVAILABLE
from homeassistant.core import HomeAssistant
from tests.common import MockConfigEntry from tests.common import MockConfigEntry
from tests.components.brother import init_integration from tests.components.brother import init_integration
async def test_async_setup_entry(hass): async def test_async_setup_entry(hass: HomeAssistant) -> None:
"""Test a successful setup entry.""" """Test a successful setup entry."""
await init_integration(hass) await init_integration(hass)
@ -19,7 +23,7 @@ async def test_async_setup_entry(hass):
assert state.state == "waiting" assert state.state == "waiting"
async def test_config_not_ready(hass): async def test_config_not_ready(hass: HomeAssistant) -> None:
"""Test for setup failure if connection to broker is missing.""" """Test for setup failure if connection to broker is missing."""
entry = MockConfigEntry( entry = MockConfigEntry(
domain=DOMAIN, domain=DOMAIN,
@ -28,13 +32,31 @@ async def test_config_not_ready(hass):
data={CONF_HOST: "localhost", CONF_TYPE: "laser"}, data={CONF_HOST: "localhost", CONF_TYPE: "laser"},
) )
with patch("brother.Brother._get_data", side_effect=ConnectionError()): with patch("brother.Brother.initialize"), patch(
"brother.Brother._get_data", side_effect=ConnectionError()
):
entry.add_to_hass(hass) entry.add_to_hass(hass)
await hass.config_entries.async_setup(entry.entry_id) await hass.config_entries.async_setup(entry.entry_id)
assert entry.state is ConfigEntryState.SETUP_RETRY assert entry.state is ConfigEntryState.SETUP_RETRY
async def test_unload_entry(hass): @pytest.mark.parametrize("exc", [(SnmpError("SNMP Error")), (ConnectionError)])
async def test_error_on_init(hass: HomeAssistant, exc: Exception) -> None:
"""Test for error on init."""
entry = MockConfigEntry(
domain=DOMAIN,
title="HL-L2340DW 0123456789",
unique_id="0123456789",
data={CONF_HOST: "localhost", CONF_TYPE: "laser"},
)
with patch("brother.Brother.initialize", side_effect=exc):
entry.add_to_hass(hass)
await hass.config_entries.async_setup(entry.entry_id)
assert entry.state is ConfigEntryState.SETUP_RETRY
async def test_unload_entry(hass: HomeAssistant) -> None:
"""Test successful unload of entry.""" """Test successful unload of entry."""
entry = await init_integration(hass) entry = await init_integration(hass)

View File

@ -19,6 +19,7 @@ from homeassistant.const import (
PERCENTAGE, PERCENTAGE,
STATE_UNAVAILABLE, STATE_UNAVAILABLE,
) )
from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_registry as er from homeassistant.helpers import entity_registry as er
from homeassistant.setup import async_setup_component from homeassistant.setup import async_setup_component
from homeassistant.util.dt import UTC, utcnow from homeassistant.util.dt import UTC, utcnow
@ -30,7 +31,7 @@ ATTR_REMAINING_PAGES = "remaining_pages"
ATTR_COUNTER = "counter" ATTR_COUNTER = "counter"
async def test_sensors(hass): async def test_sensors(hass: HomeAssistant) -> None:
"""Test states of the sensors.""" """Test states of the sensors."""
entry = await init_integration(hass, skip_setup=True) entry = await init_integration(hass, skip_setup=True)
@ -45,7 +46,9 @@ async def test_sensors(hass):
disabled_by=None, disabled_by=None,
) )
test_time = datetime(2019, 11, 11, 9, 10, 32, tzinfo=UTC) test_time = datetime(2019, 11, 11, 9, 10, 32, tzinfo=UTC)
with patch("brother.datetime", utcnow=Mock(return_value=test_time)), patch( with patch("brother.Brother.initialize"), patch(
"brother.datetime", utcnow=Mock(return_value=test_time)
), patch(
"brother.Brother._get_data", "brother.Brother._get_data",
return_value=json.loads(load_fixture("printer_data.json", "brother")), return_value=json.loads(load_fixture("printer_data.json", "brother")),
): ):
@ -235,7 +238,7 @@ async def test_sensors(hass):
entry = registry.async_get("sensor.hl_l2340dw_b_w_counter") entry = registry.async_get("sensor.hl_l2340dw_b_w_counter")
assert entry assert entry
assert entry.unique_id == "0123456789_b/w_counter" assert entry.unique_id == "0123456789_bw_counter"
state = hass.states.get("sensor.hl_l2340dw_color_counter") state = hass.states.get("sensor.hl_l2340dw_color_counter")
assert state assert state
@ -276,7 +279,7 @@ async def test_disabled_by_default_sensors(hass):
assert entry.disabled_by is er.RegistryEntryDisabler.INTEGRATION assert entry.disabled_by is er.RegistryEntryDisabler.INTEGRATION
async def test_availability(hass): async def test_availability(hass: HomeAssistant) -> None:
"""Ensure that we mark the entities unavailable correctly when device is offline.""" """Ensure that we mark the entities unavailable correctly when device is offline."""
await init_integration(hass) await init_integration(hass)
@ -286,7 +289,9 @@ async def test_availability(hass):
assert state.state == "waiting" assert state.state == "waiting"
future = utcnow() + timedelta(minutes=5) future = utcnow() + timedelta(minutes=5)
with patch("brother.Brother._get_data", side_effect=ConnectionError()): with patch("brother.Brother.initialize"), patch(
"brother.Brother._get_data", side_effect=ConnectionError()
):
async_fire_time_changed(hass, future) async_fire_time_changed(hass, future)
await hass.async_block_till_done() await hass.async_block_till_done()
@ -295,7 +300,7 @@ async def test_availability(hass):
assert state.state == STATE_UNAVAILABLE assert state.state == STATE_UNAVAILABLE
future = utcnow() + timedelta(minutes=10) future = utcnow() + timedelta(minutes=10)
with patch( with patch("brother.Brother.initialize"), patch(
"brother.Brother._get_data", "brother.Brother._get_data",
return_value=json.loads(load_fixture("printer_data.json", "brother")), return_value=json.loads(load_fixture("printer_data.json", "brother")),
): ):
@ -308,7 +313,7 @@ async def test_availability(hass):
assert state.state == "waiting" assert state.state == "waiting"
async def test_manual_update_entity(hass): async def test_manual_update_entity(hass: HomeAssistant) -> None:
"""Test manual update entity via service homeassistant/update_entity.""" """Test manual update entity via service homeassistant/update_entity."""
await init_integration(hass) await init_integration(hass)
@ -326,3 +331,22 @@ async def test_manual_update_entity(hass):
) )
assert len(mock_update.mock_calls) == 1 assert len(mock_update.mock_calls) == 1
async def test_unique_id_migration(hass: HomeAssistant) -> None:
"""Test states of the unique_id migration."""
registry = er.async_get(hass)
registry.async_get_or_create(
SENSOR_DOMAIN,
DOMAIN,
"0123456789_b/w_counter",
suggested_object_id="hl_l2340dw_b_w_counter",
disabled_by=None,
)
await init_integration(hass)
entry = registry.async_get("sensor.hl_l2340dw_b_w_counter")
assert entry
assert entry.unique_id == "0123456789_bw_counter"