Improve the discovery process for Gree (#45449)

* Add support for async device discovery

* FIx missing dispatcher cleanup breaking integration reload

* Update homeassistant/components/gree/climate.py

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

* Update homeassistant/components/gree/switch.py

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

* Update homeassistant/components/gree/bridge.py

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

* Working on feedback

* Improving load/unload tests

* Update homeassistant/components/gree/__init__.py

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

* Working on more feedback

* Add tests covering async discovery scenarios

* Remove unnecessary shutdown

* Update homeassistant/components/gree/__init__.py

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

* Code refactor from reviews

Co-authored-by: Erik Montnemery <erik@montnemery.com>
This commit is contained in:
Clifford Roche 2021-04-13 05:54:03 -04:00 committed by GitHub
parent 63d42867e8
commit 4ce6d00a22
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 357 additions and 180 deletions

View File

@ -1,14 +1,23 @@
"""The Gree Climate integration."""
import asyncio
from datetime import timedelta
import logging
from homeassistant.components.climate import DOMAIN as CLIMATE_DOMAIN
from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.helpers.event import async_track_time_interval
from .bridge import CannotConnect, DeviceDataUpdateCoordinator, DeviceHelper
from .const import COORDINATOR, DOMAIN
from .bridge import DiscoveryService
from .const import (
COORDINATORS,
DATA_DISCOVERY_INTERVAL,
DATA_DISCOVERY_SERVICE,
DISCOVERY_SCAN_INTERVAL,
DISPATCHERS,
DOMAIN,
)
_LOGGER = logging.getLogger(__name__)
@ -21,31 +30,11 @@ async def async_setup(hass: HomeAssistant, config: dict):
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry):
"""Set up Gree Climate from a config entry."""
devices = []
gree_discovery = DiscoveryService(hass)
hass.data[DATA_DISCOVERY_SERVICE] = gree_discovery
# First we'll grab as many devices as we can find on the network
# it's necessary to bind static devices anyway
_LOGGER.debug("Scanning network for Gree devices")
hass.data[DOMAIN].setdefault(DISPATCHERS, [])
for device_info in await DeviceHelper.find_devices():
try:
device = await DeviceHelper.try_bind_device(device_info)
except CannotConnect:
_LOGGER.error("Unable to bind to gree device: %s", device_info)
continue
_LOGGER.debug(
"Adding Gree device at %s:%i (%s)",
device.device_info.ip,
device.device_info.port,
device.device_info.name,
)
devices.append(device)
coordinators = [DeviceDataUpdateCoordinator(hass, d) for d in devices]
await asyncio.gather(*[x.async_refresh() for x in coordinators])
hass.data[DOMAIN][COORDINATOR] = coordinators
hass.async_create_task(
hass.config_entries.async_forward_entry_setup(entry, CLIMATE_DOMAIN)
)
@ -53,11 +42,31 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry):
hass.config_entries.async_forward_entry_setup(entry, SWITCH_DOMAIN)
)
async def _async_scan_update(_=None):
await gree_discovery.discovery.scan()
_LOGGER.debug("Scanning network for Gree devices")
await _async_scan_update()
hass.data[DOMAIN][DATA_DISCOVERY_INTERVAL] = async_track_time_interval(
hass, _async_scan_update, timedelta(seconds=DISCOVERY_SCAN_INTERVAL)
)
return True
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry):
"""Unload a config entry."""
if hass.data[DOMAIN].get(DISPATCHERS) is not None:
for cleanup in hass.data[DOMAIN][DISPATCHERS]:
cleanup()
if hass.data[DOMAIN].get(DATA_DISCOVERY_INTERVAL) is not None:
hass.data[DOMAIN].pop(DATA_DISCOVERY_INTERVAL)()
if hass.data.get(DATA_DISCOVERY_SERVICE) is not None:
hass.data.pop(DATA_DISCOVERY_SERVICE)
results = asyncio.gather(
hass.config_entries.async_forward_entry_unload(entry, CLIMATE_DOMAIN),
hass.config_entries.async_forward_entry_unload(entry, SWITCH_DOMAIN),
@ -65,8 +74,7 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry):
unload_ok = all(await results)
if unload_ok:
hass.data[DOMAIN].pop("devices", None)
hass.data[DOMAIN].pop(CLIMATE_DOMAIN, None)
hass.data[DOMAIN].pop(SWITCH_DOMAIN, None)
hass.data[DOMAIN].pop(COORDINATORS, None)
hass.data[DOMAIN].pop(DISPATCHERS, None)
return unload_ok

View File

@ -5,14 +5,20 @@ from datetime import timedelta
import logging
from greeclimate.device import Device, DeviceInfo
from greeclimate.discovery import Discovery
from greeclimate.discovery import Discovery, Listener
from greeclimate.exceptions import DeviceNotBoundError, DeviceTimeoutError
from homeassistant import exceptions
from homeassistant.core import HomeAssistant
from homeassistant.helpers.dispatcher import async_dispatcher_send
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
from .const import DOMAIN, MAX_ERRORS
from .const import (
COORDINATORS,
DISCOVERY_TIMEOUT,
DISPATCH_DEVICE_DISCOVERED,
DOMAIN,
MAX_ERRORS,
)
_LOGGER = logging.getLogger(__name__)
@ -36,6 +42,8 @@ class DeviceDataUpdateCoordinator(DataUpdateCoordinator):
"""Update the state of the device."""
try:
await self.device.update_state()
except DeviceNotBoundError as error:
raise UpdateFailed(f"Device {self.name} is unavailable") from error
except DeviceTimeoutError as error:
self._error_count += 1
@ -46,16 +54,7 @@ class DeviceDataUpdateCoordinator(DataUpdateCoordinator):
self.name,
self.device.device_info,
)
raise UpdateFailed(error) from error
else:
if not self.last_update_success and self._error_count:
_LOGGER.warning(
"Device is available: %s (%s)",
self.name,
str(self.device.device_info),
)
self._error_count = 0
raise UpdateFailed(f"Device {self.name} is unavailable") from error
async def push_state_update(self):
"""Send state updates to the physical device."""
@ -69,28 +68,38 @@ class DeviceDataUpdateCoordinator(DataUpdateCoordinator):
)
class DeviceHelper:
"""Device search and bind wrapper for Gree platform."""
class DiscoveryService(Listener):
"""Discovery event handler for gree devices."""
@staticmethod
async def try_bind_device(device_info: DeviceInfo) -> Device:
"""Try and bing with a discovered device.
def __init__(self, hass) -> None:
"""Initialize discovery service."""
super().__init__()
self.hass = hass
self.discovery = Discovery(DISCOVERY_TIMEOUT)
self.discovery.add_listener(self)
hass.data[DOMAIN].setdefault(COORDINATORS, [])
async def device_found(self, device_info: DeviceInfo) -> None:
"""Handle new device found on the network."""
Note the you must bind with the device very quickly after it is discovered, or the
process may not be completed correctly, raising a `CannotConnect` error.
"""
device = Device(device_info)
try:
await device.bind()
except DeviceNotBoundError as exception:
raise CannotConnect from exception
return device
except DeviceNotBoundError:
_LOGGER.error("Unable to bind to gree device: %s", device_info)
except DeviceTimeoutError:
_LOGGER.error("Timeout trying to bind to gree device: %s", device_info)
@staticmethod
async def find_devices() -> list[DeviceInfo]:
"""Gather a list of device infos from the local network."""
return await Discovery.search_devices()
_LOGGER.info(
"Adding Gree device %s at %s:%i",
device.device_info.name,
device.device_info.ip,
device.device_info.port,
)
coordo = DeviceDataUpdateCoordinator(self.hass, device)
self.hass.data[DOMAIN][COORDINATORS].append(coordo)
await coordo.async_refresh()
class CannotConnect(exceptions.HomeAssistantError):
"""Error to indicate we cannot connect."""
async_dispatcher_send(self.hass, DISPATCH_DEVICE_DISCOVERED, coordo)

View File

@ -43,11 +43,15 @@ from homeassistant.const import (
TEMP_CELSIUS,
TEMP_FAHRENHEIT,
)
from homeassistant.core import callback
from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC
from homeassistant.helpers.dispatcher import async_dispatcher_connect
from homeassistant.helpers.update_coordinator import CoordinatorEntity
from .const import (
COORDINATOR,
COORDINATORS,
DISPATCH_DEVICE_DISCOVERED,
DISPATCHERS,
DOMAIN,
FAN_MEDIUM_HIGH,
FAN_MEDIUM_LOW,
@ -97,11 +101,17 @@ SUPPORTED_FEATURES = (
async def async_setup_entry(hass, config_entry, async_add_entities):
"""Set up the Gree HVAC device from a config entry."""
async_add_entities(
[
GreeClimateEntity(coordinator)
for coordinator in hass.data[DOMAIN][COORDINATOR]
]
@callback
def init_device(coordinator):
"""Register the device."""
async_add_entities([GreeClimateEntity(coordinator)])
for coordinator in hass.data[DOMAIN][COORDINATORS]:
init_device(coordinator)
hass.data[DOMAIN][DISPATCHERS].append(
async_dispatcher_connect(hass, DISPATCH_DEVICE_DISCOVERED, init_device)
)

View File

@ -1,14 +1,16 @@
"""Config flow for Gree."""
from greeclimate.discovery import Discovery
from homeassistant import config_entries
from homeassistant.helpers import config_entry_flow
from .bridge import DeviceHelper
from .const import DOMAIN
from .const import DISCOVERY_TIMEOUT, DOMAIN
async def _async_has_devices(hass) -> bool:
"""Return if there are devices that can be discovered."""
devices = await DeviceHelper.find_devices()
gree_discovery = Discovery(DISCOVERY_TIMEOUT)
devices = await gree_discovery.scan(wait_for=DISCOVERY_TIMEOUT)
return len(devices) > 0

View File

@ -1,5 +1,15 @@
"""Constants for the Gree Climate integration."""
COORDINATORS = "coordinators"
DATA_DISCOVERY_SERVICE = "gree_discovery"
DATA_DISCOVERY_INTERVAL = "gree_discovery_interval"
DISCOVERY_SCAN_INTERVAL = 300
DISCOVERY_TIMEOUT = 8
DISPATCH_DEVICE_DISCOVERED = "gree_device_discovered"
DISPATCHERS = "dispatchers"
DOMAIN = "gree"
COORDINATOR = "coordinator"

View File

@ -3,6 +3,6 @@
"name": "Gree Climate",
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/gree",
"requirements": ["greeclimate==0.10.3"],
"requirements": ["greeclimate==0.11.4"],
"codeowners": ["@cmroche"]
}

View File

@ -2,19 +2,27 @@
from __future__ import annotations
from homeassistant.components.switch import DEVICE_CLASS_SWITCH, SwitchEntity
from homeassistant.core import callback
from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC
from homeassistant.helpers.dispatcher import async_dispatcher_connect
from homeassistant.helpers.update_coordinator import CoordinatorEntity
from .const import COORDINATOR, DOMAIN
from .const import COORDINATORS, DISPATCH_DEVICE_DISCOVERED, DISPATCHERS, DOMAIN
async def async_setup_entry(hass, config_entry, async_add_entities):
"""Set up the Gree HVAC device from a config entry."""
async_add_entities(
[
GreeSwitchEntity(coordinator)
for coordinator in hass.data[DOMAIN][COORDINATOR]
]
@callback
def init_device(coordinator):
"""Register the device."""
async_add_entities([GreeSwitchEntity(coordinator)])
for coordinator in hass.data[DOMAIN][COORDINATORS]:
init_device(coordinator)
hass.data[DOMAIN][DISPATCHERS].append(
async_dispatcher_connect(hass, DISPATCH_DEVICE_DISCOVERED, init_device)
)

View File

@ -699,7 +699,7 @@ gpiozero==1.5.1
gps3==0.33.3
# homeassistant.components.gree
greeclimate==0.10.3
greeclimate==0.11.4
# homeassistant.components.greeneye_monitor
greeneye_monitor==2.1

View File

@ -384,7 +384,7 @@ google-nest-sdm==0.2.12
googlemaps==2.5.1
# homeassistant.components.gree
greeclimate==0.10.3
greeclimate==0.11.4
# homeassistant.components.profiler
guppy3==3.1.0

View File

@ -1,6 +1,43 @@
"""Common helpers for gree test cases."""
import asyncio
import logging
from unittest.mock import AsyncMock, Mock
from greeclimate.discovery import Listener
from homeassistant.components.gree.const import DISCOVERY_TIMEOUT
_LOGGER = logging.getLogger(__name__)
class FakeDiscovery:
"""Mock class replacing Gree device discovery."""
def __init__(self, timeout: int = DISCOVERY_TIMEOUT) -> None:
"""Initialize the class."""
self.mock_devices = [build_device_mock()]
self.timeout = timeout
self._listeners = []
self.scan_count = 0
def add_listener(self, listener: Listener) -> None:
"""Add an event listener."""
self._listeners.append(listener)
async def scan(self, wait_for: int = 0):
"""Search for devices, return mocked data."""
self.scan_count += 1
_LOGGER.info("CALLED SCAN %d TIMES", self.scan_count)
infos = [x.device_info for x in self.mock_devices]
for listener in self._listeners:
[await listener.device_found(x) for x in infos]
if wait_for:
await asyncio.sleep(wait_for)
return infos
def build_device_info_mock(
name="fake-device-1", ipAddress="1.1.1.1", mac="aabbcc112233"

View File

@ -1,36 +1,24 @@
"""Pytest module configuration."""
from unittest.mock import AsyncMock, patch
from unittest.mock import patch
import pytest
from .common import build_device_info_mock, build_device_mock
from .common import FakeDiscovery, build_device_mock
@pytest.fixture(name="discovery")
@pytest.fixture(autouse=True, name="discovery")
def discovery_fixture():
"""Patch the discovery service."""
with patch(
"homeassistant.components.gree.bridge.Discovery.search_devices",
new_callable=AsyncMock,
return_value=[build_device_info_mock()],
) as mock:
"""Patch the discovery object."""
with patch("homeassistant.components.gree.bridge.Discovery") as mock:
mock.return_value = FakeDiscovery()
yield mock
@pytest.fixture(name="device")
@pytest.fixture(autouse=True, name="device")
def device_fixture():
"""Path the device search and bind."""
"""Patch the device search and bind."""
with patch(
"homeassistant.components.gree.bridge.Device",
return_value=build_device_mock(),
) as mock:
yield mock
@pytest.fixture(name="setup")
def setup_fixture():
"""Patch the climate setup."""
with patch(
"homeassistant.components.gree.climate.async_setup_entry", return_value=True
) as setup:
yield setup

View File

@ -97,7 +97,7 @@ async def test_discovery_setup(hass, discovery, device):
name="fake-device-2", ipAddress="2.2.2.2", mac="bbccdd223344"
)
discovery.return_value = [MockDevice1.device_info, MockDevice2.device_info]
discovery.return_value.mock_devices = [MockDevice1, MockDevice2]
device.side_effect = [MockDevice1, MockDevice2]
await async_setup_gree(hass)
@ -106,24 +106,127 @@ async def test_discovery_setup(hass, discovery, device):
assert len(hass.states.async_all(DOMAIN)) == 2
async def test_discovery_setup_connection_error(hass, discovery, device):
async def test_discovery_setup_connection_error(hass, discovery, device, mock_now):
"""Test gree integration is setup."""
MockDevice1 = build_device_mock(name="fake-device-1")
MockDevice1 = build_device_mock(
name="fake-device-1", ipAddress="1.1.1.1", mac="aabbcc112233"
)
MockDevice1.bind = AsyncMock(side_effect=DeviceNotBoundError)
MockDevice1.update_state = AsyncMock(side_effect=DeviceNotBoundError)
discovery.return_value.mock_devices = [MockDevice1]
device.return_value = MockDevice1
await async_setup_gree(hass)
await hass.async_block_till_done()
assert len(hass.states.async_all(DOMAIN)) == 1
state = hass.states.get(ENTITY_ID)
assert state.name == "fake-device-1"
assert state.state == STATE_UNAVAILABLE
async def test_discovery_after_setup(hass, discovery, device, mock_now):
"""Test gree devices don't change after multiple discoveries."""
MockDevice1 = build_device_mock(
name="fake-device-1", ipAddress="1.1.1.1", mac="aabbcc112233"
)
MockDevice1.bind = AsyncMock(side_effect=DeviceNotBoundError)
MockDevice2 = build_device_mock(name="fake-device-2")
MockDevice2.bind = AsyncMock(side_effect=DeviceNotBoundError)
MockDevice2 = build_device_mock(
name="fake-device-2", ipAddress="2.2.2.2", mac="bbccdd223344"
)
MockDevice2.bind = AsyncMock(side_effect=DeviceTimeoutError)
discovery.return_value.mock_devices = [MockDevice1, MockDevice2]
device.side_effect = [MockDevice1, MockDevice2]
await async_setup_gree(hass)
await hass.async_block_till_done()
assert discovery.call_count == 1
assert not hass.states.async_all(DOMAIN)
assert discovery.return_value.scan_count == 1
assert len(hass.states.async_all(DOMAIN)) == 2
# rediscover the same devices shouldn't change anything
discovery.return_value.mock_devices = [MockDevice1, MockDevice2]
device.side_effect = [MockDevice1, MockDevice2]
next_update = mock_now + timedelta(minutes=6)
with patch("homeassistant.util.dt.utcnow", return_value=next_update):
async_fire_time_changed(hass, next_update)
await hass.async_block_till_done()
assert discovery.return_value.scan_count == 2
assert len(hass.states.async_all(DOMAIN)) == 2
async def test_update_connection_failure(hass, discovery, device, mock_now):
async def test_discovery_add_device_after_setup(hass, discovery, device, mock_now):
"""Test gree devices can be added after initial setup."""
MockDevice1 = build_device_mock(
name="fake-device-1", ipAddress="1.1.1.1", mac="aabbcc112233"
)
MockDevice1.bind = AsyncMock(side_effect=DeviceNotBoundError)
MockDevice2 = build_device_mock(
name="fake-device-2", ipAddress="2.2.2.2", mac="bbccdd223344"
)
MockDevice2.bind = AsyncMock(side_effect=DeviceTimeoutError)
discovery.return_value.mock_devices = [MockDevice1]
device.side_effect = [MockDevice1]
await async_setup_gree(hass)
await hass.async_block_till_done()
assert discovery.return_value.scan_count == 1
assert len(hass.states.async_all(DOMAIN)) == 1
# rediscover the same devices shouldn't change anything
discovery.return_value.mock_devices = [MockDevice2]
device.side_effect = [MockDevice2]
next_update = mock_now + timedelta(minutes=6)
with patch("homeassistant.util.dt.utcnow", return_value=next_update):
async_fire_time_changed(hass, next_update)
await hass.async_block_till_done()
assert discovery.return_value.scan_count == 2
assert len(hass.states.async_all(DOMAIN)) == 2
async def test_discovery_device_bind_after_setup(hass, discovery, device, mock_now):
"""Test gree devices can be added after a late device bind."""
MockDevice1 = build_device_mock(
name="fake-device-1", ipAddress="1.1.1.1", mac="aabbcc112233"
)
MockDevice1.bind = AsyncMock(side_effect=DeviceNotBoundError)
MockDevice1.update_state = AsyncMock(side_effect=DeviceNotBoundError)
discovery.return_value.mock_devices = [MockDevice1]
device.return_value = MockDevice1
await async_setup_gree(hass)
await hass.async_block_till_done()
assert len(hass.states.async_all(DOMAIN)) == 1
state = hass.states.get(ENTITY_ID)
assert state.name == "fake-device-1"
assert state.state == STATE_UNAVAILABLE
# Now the device becomes available
MockDevice1.bind.side_effect = None
MockDevice1.update_state.side_effect = None
next_update = mock_now + timedelta(minutes=5)
with patch("homeassistant.util.dt.utcnow", return_value=next_update):
async_fire_time_changed(hass, next_update)
await hass.async_block_till_done()
state = hass.states.get(ENTITY_ID)
assert state.state != STATE_UNAVAILABLE
async def test_update_connection_failure(hass, device, mock_now):
"""Testing update hvac connection failure exception."""
device().update_state.side_effect = [
DEFAULT_MOCK,
@ -229,11 +332,10 @@ async def test_send_command_device_timeout(hass, discovery, device, mock_now):
# Send failure should not raise exceptions or change device state
assert await hass.services.async_call(
DOMAIN,
SERVICE_SET_HVAC_MODE,
{ATTR_ENTITY_ID: ENTITY_ID, ATTR_HVAC_MODE: HVAC_MODE_AUTO},
SERVICE_TURN_ON,
{ATTR_ENTITY_ID: ENTITY_ID},
blocking=True,
)
await hass.async_block_till_done()
state = hass.states.get(ENTITY_ID)
assert state is not None
@ -244,45 +346,6 @@ async def test_send_power_on(hass, discovery, device, mock_now):
"""Test for sending power on command to the device."""
await async_setup_gree(hass)
assert await hass.services.async_call(
DOMAIN,
SERVICE_TURN_ON,
{ATTR_ENTITY_ID: ENTITY_ID},
blocking=True,
)
state = hass.states.get(ENTITY_ID)
assert state is not None
assert state.state != HVAC_MODE_OFF
async def test_send_power_on_device_timeout(hass, discovery, device, mock_now):
"""Test for sending power on command to the device with a device timeout."""
device().push_state_update.side_effect = DeviceTimeoutError
await async_setup_gree(hass)
assert await hass.services.async_call(
DOMAIN,
SERVICE_TURN_ON,
{ATTR_ENTITY_ID: ENTITY_ID},
blocking=True,
)
state = hass.states.get(ENTITY_ID)
assert state is not None
assert state.state != HVAC_MODE_OFF
async def test_send_power_off(hass, discovery, device, mock_now):
"""Test for sending power off command to the device."""
await async_setup_gree(hass)
next_update = mock_now + timedelta(minutes=5)
with patch("homeassistant.util.dt.utcnow", return_value=next_update):
async_fire_time_changed(hass, next_update)
await hass.async_block_till_done()
assert await hass.services.async_call(
DOMAIN,
SERVICE_TURN_OFF,
@ -301,11 +364,6 @@ async def test_send_power_off_device_timeout(hass, discovery, device, mock_now):
await async_setup_gree(hass)
next_update = mock_now + timedelta(minutes=5)
with patch("homeassistant.util.dt.utcnow", return_value=next_update):
async_fire_time_changed(hass, next_update)
await hass.async_block_till_done()
assert await hass.services.async_call(
DOMAIN,
SERVICE_TURN_OFF,

View File

@ -1,20 +1,60 @@
"""Tests for the Gree Integration."""
from unittest.mock import patch
from homeassistant import config_entries, data_entry_flow
from homeassistant.components.gree.const import DOMAIN as GREE_DOMAIN
from .common import FakeDiscovery
async def test_creating_entry_sets_up_climate(hass, discovery, device, setup):
async def test_creating_entry_sets_up_climate(hass):
"""Test setting up Gree creates the climate components."""
result = await hass.config_entries.flow.async_init(
GREE_DOMAIN, context={"source": config_entries.SOURCE_USER}
)
with patch(
"homeassistant.components.gree.climate.async_setup_entry", return_value=True
) as setup, patch(
"homeassistant.components.gree.bridge.Discovery", return_value=FakeDiscovery()
), patch(
"homeassistant.components.gree.config_flow.Discovery",
return_value=FakeDiscovery(),
):
result = await hass.config_entries.flow.async_init(
GREE_DOMAIN, context={"source": config_entries.SOURCE_USER}
)
# Confirmation form
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
# Confirmation form
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
result = await hass.config_entries.flow.async_configure(result["flow_id"], {})
assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
result = await hass.config_entries.flow.async_configure(result["flow_id"], {})
assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
await hass.async_block_till_done()
await hass.async_block_till_done()
assert len(setup.mock_calls) == 1
assert len(setup.mock_calls) == 1
async def test_creating_entry_has_no_devices(hass):
"""Test setting up Gree creates the climate components."""
with patch(
"homeassistant.components.gree.climate.async_setup_entry", return_value=True
) as setup, patch(
"homeassistant.components.gree.bridge.Discovery", return_value=FakeDiscovery()
) as discovery, patch(
"homeassistant.components.gree.config_flow.Discovery",
return_value=FakeDiscovery(),
) as discovery2:
discovery.return_value.mock_devices = []
discovery2.return_value.mock_devices = []
result = await hass.config_entries.flow.async_init(
GREE_DOMAIN, context={"source": config_entries.SOURCE_USER}
)
# Confirmation form
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
result = await hass.config_entries.flow.async_configure(result["flow_id"], {})
assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT
await hass.async_block_till_done()
assert len(setup.mock_calls) == 0

View File

@ -1,5 +1,4 @@
"""Tests for the Gree Integration."""
from unittest.mock import patch
from homeassistant.components.gree.const import DOMAIN as GREE_DOMAIN
@ -9,31 +8,39 @@ from homeassistant.setup import async_setup_component
from tests.common import MockConfigEntry
async def test_setup_simple(hass, discovery, device):
async def test_setup_simple(hass):
"""Test gree integration is setup."""
await async_setup_component(hass, GREE_DOMAIN, {})
await hass.async_block_till_done()
# No flows started
assert len(hass.config_entries.flow.async_progress()) == 0
async def test_unload_config_entry(hass, discovery, device):
"""Test that the async_unload_entry works."""
# As we have currently no configuration, we just to pass the domain here.
entry = MockConfigEntry(domain=GREE_DOMAIN)
entry.add_to_hass(hass)
with patch(
"homeassistant.components.gree.climate.async_setup_entry",
return_value=True,
) as climate_setup:
) as climate_setup, patch(
"homeassistant.components.gree.switch.async_setup_entry",
return_value=True,
) as switch_setup:
assert await async_setup_component(hass, GREE_DOMAIN, {})
await hass.async_block_till_done()
assert len(climate_setup.mock_calls) == 1
assert len(switch_setup.mock_calls) == 1
assert entry.state == ENTRY_STATE_LOADED
# No flows started
assert len(hass.config_entries.flow.async_progress()) == 0
async def test_unload_config_entry(hass):
"""Test that the async_unload_entry works."""
# As we have currently no configuration, we just to pass the domain here.
entry = MockConfigEntry(domain=GREE_DOMAIN)
entry.add_to_hass(hass)
assert await async_setup_component(hass, GREE_DOMAIN, {})
await hass.async_block_till_done()
await hass.config_entries.async_unload(entry.entry_id)
await hass.async_block_till_done()
assert entry.state == ENTRY_STATE_NOT_LOADED

View File

@ -26,7 +26,7 @@ async def async_setup_gree(hass):
await hass.async_block_till_done()
async def test_send_panel_light_on(hass, discovery, device):
async def test_send_panel_light_on(hass):
"""Test for sending power on command to the device."""
await async_setup_gree(hass)
@ -42,7 +42,7 @@ async def test_send_panel_light_on(hass, discovery, device):
assert state.state == STATE_ON
async def test_send_panel_light_on_device_timeout(hass, discovery, device):
async def test_send_panel_light_on_device_timeout(hass, device):
"""Test for sending power on command to the device with a device timeout."""
device().push_state_update.side_effect = DeviceTimeoutError
@ -60,7 +60,7 @@ async def test_send_panel_light_on_device_timeout(hass, discovery, device):
assert state.state == STATE_ON
async def test_send_panel_light_off(hass, discovery, device):
async def test_send_panel_light_off(hass):
"""Test for sending power on command to the device."""
await async_setup_gree(hass)
@ -76,7 +76,7 @@ async def test_send_panel_light_off(hass, discovery, device):
assert state.state == STATE_OFF
async def test_send_panel_light_toggle(hass, discovery, device):
async def test_send_panel_light_toggle(hass):
"""Test for sending power on command to the device."""
await async_setup_gree(hass)
@ -117,7 +117,7 @@ async def test_send_panel_light_toggle(hass, discovery, device):
assert state.state == STATE_ON
async def test_panel_light_name(hass, discovery, device):
async def test_panel_light_name(hass):
"""Test for name property."""
await async_setup_gree(hass)
state = hass.states.get(ENTITY_ID)