Add iSmartGate support (#39437)

* Add iSmartGate support.

* Addressing PR feedback.

* More PR feedback cleanups.
This commit is contained in:
Robert Van Gorkom 2020-09-05 07:26:01 -07:00 committed by GitHub
parent 52c09396e0
commit b860caa631
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 458 additions and 598 deletions

View File

@ -1,10 +1,12 @@
"""The gogogate2 component.""" """The gogogate2 component."""
from homeassistant.components.cover import DOMAIN as COVER_DOMAIN from homeassistant.components.cover import DOMAIN as COVER_DOMAIN
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_DEVICE
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.exceptions import ConfigEntryNotReady
from .common import get_data_update_coordinator from .common import get_data_update_coordinator
from .const import DEVICE_TYPE_GOGOGATE2
async def async_setup(hass: HomeAssistant, base_config: dict) -> bool: async def async_setup(hass: HomeAssistant, base_config: dict) -> bool:
@ -14,6 +16,18 @@ async def async_setup(hass: HomeAssistant, base_config: dict) -> bool:
async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool: async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool:
"""Do setup of Gogogate2.""" """Do setup of Gogogate2."""
# Update the config entry.
config_updates = {}
if CONF_DEVICE not in config_entry.data:
config_updates["data"] = {
**config_entry.data,
**{CONF_DEVICE: DEVICE_TYPE_GOGOGATE2},
}
if config_updates:
hass.config_entries.async_update_entry(config_entry, **config_updates)
data_update_coordinator = get_data_update_coordinator(hass, config_entry) data_update_coordinator = get_data_update_coordinator(hass, config_entry)
await data_update_coordinator.async_refresh() await data_update_coordinator.async_refresh()

View File

@ -4,16 +4,21 @@ import logging
from typing import Awaitable, Callable, NamedTuple, Optional from typing import Awaitable, Callable, NamedTuple, Optional
import async_timeout import async_timeout
from gogogate2_api import GogoGate2Api from gogogate2_api import AbstractGateApi, GogoGate2Api, ISmartGateApi
from gogogate2_api.common import Door from gogogate2_api.common import AbstractDoor
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_IP_ADDRESS, CONF_PASSWORD, CONF_USERNAME from homeassistant.const import (
CONF_DEVICE,
CONF_IP_ADDRESS,
CONF_PASSWORD,
CONF_USERNAME,
)
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.helpers.debounce import Debouncer from homeassistant.helpers.debounce import Debouncer
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
from .const import DATA_UPDATE_COORDINATOR, DOMAIN from .const import DATA_UPDATE_COORDINATOR, DEVICE_TYPE_ISMARTGATE, DOMAIN
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -23,17 +28,17 @@ class StateData(NamedTuple):
config_unique_id: str config_unique_id: str
unique_id: Optional[str] unique_id: Optional[str]
door: Optional[Door] door: Optional[AbstractDoor]
class GogoGateDataUpdateCoordinator(DataUpdateCoordinator): class DeviceDataUpdateCoordinator(DataUpdateCoordinator):
"""Manages polling for state changes from the device.""" """Manages polling for state changes from the device."""
def __init__( def __init__(
self, self,
hass: HomeAssistant, hass: HomeAssistant,
logger: logging.Logger, logger: logging.Logger,
api: GogoGate2Api, api: AbstractGateApi,
*, *,
name: str, name: str,
update_interval: timedelta, update_interval: timedelta,
@ -55,7 +60,7 @@ class GogoGateDataUpdateCoordinator(DataUpdateCoordinator):
def get_data_update_coordinator( def get_data_update_coordinator(
hass: HomeAssistant, config_entry: ConfigEntry hass: HomeAssistant, config_entry: ConfigEntry
) -> GogoGateDataUpdateCoordinator: ) -> DeviceDataUpdateCoordinator:
"""Get an update coordinator.""" """Get an update coordinator."""
hass.data.setdefault(DOMAIN, {}) hass.data.setdefault(DOMAIN, {})
hass.data[DOMAIN].setdefault(config_entry.entry_id, {}) hass.data[DOMAIN].setdefault(config_entry.entry_id, {})
@ -73,7 +78,7 @@ def get_data_update_coordinator(
f"Error communicating with API: {exception}" f"Error communicating with API: {exception}"
) from exception ) from exception
config_entry_data[DATA_UPDATE_COORDINATOR] = GogoGateDataUpdateCoordinator( config_entry_data[DATA_UPDATE_COORDINATOR] = DeviceDataUpdateCoordinator(
hass, hass,
_LOGGER, _LOGGER,
api, api,
@ -87,14 +92,19 @@ def get_data_update_coordinator(
return config_entry_data[DATA_UPDATE_COORDINATOR] return config_entry_data[DATA_UPDATE_COORDINATOR]
def cover_unique_id(config_entry: ConfigEntry, door: Door) -> str: def cover_unique_id(config_entry: ConfigEntry, door: AbstractDoor) -> str:
"""Generate a cover entity unique id.""" """Generate a cover entity unique id."""
return f"{config_entry.unique_id}_{door.door_id}" return f"{config_entry.unique_id}_{door.door_id}"
def get_api(config_data: dict) -> GogoGate2Api: def get_api(config_data: dict) -> AbstractGateApi:
"""Get an api object for config data.""" """Get an api object for config data."""
return GogoGate2Api( gate_class = GogoGate2Api
if config_data[CONF_DEVICE] == DEVICE_TYPE_ISMARTGATE:
gate_class = ISmartGateApi
return gate_class(
config_data[CONF_IP_ADDRESS], config_data[CONF_IP_ADDRESS],
config_data[CONF_USERNAME], config_data[CONF_USERNAME],
config_data[CONF_PASSWORD], config_data[CONF_PASSWORD],

View File

@ -1,16 +1,23 @@
"""Config flow for Gogogate2.""" """Config flow for Gogogate2."""
import dataclasses
import logging import logging
import re import re
from gogogate2_api.common import ApiError from gogogate2_api.common import AbstractInfoResponse, ApiError
from gogogate2_api.const import ApiErrorCode from gogogate2_api.const import GogoGate2ApiErrorCode, ISmartGateApiErrorCode
import voluptuous as vol import voluptuous as vol
from homeassistant import config_entries from homeassistant import config_entries
from homeassistant.config_entries import SOURCE_IMPORT, ConfigFlow from homeassistant.config_entries import SOURCE_IMPORT, ConfigFlow
from homeassistant.const import CONF_IP_ADDRESS, CONF_PASSWORD, CONF_USERNAME from homeassistant.const import (
CONF_DEVICE,
CONF_IP_ADDRESS,
CONF_PASSWORD,
CONF_USERNAME,
)
from .common import get_api from .common import get_api
from .const import DEVICE_TYPE_GOGOGATE2, DEVICE_TYPE_ISMARTGATE
from .const import DOMAIN # pylint: disable=unused-import from .const import DOMAIN # pylint: disable=unused-import
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -36,15 +43,35 @@ class Gogogate2FlowHandler(ConfigFlow, domain=DOMAIN):
if user_input: if user_input:
api = get_api(user_input) api = get_api(user_input)
try: try:
data = await self.hass.async_add_executor_job(api.info) data: AbstractInfoResponse = await self.hass.async_add_executor_job(
api.info
)
data_dict = dataclasses.asdict(data)
title = data_dict.get(
"gogogatename", data_dict.get("ismartgatename", "Cover")
)
await self.async_set_unique_id(re.sub("\\..*$", "", data.remoteaccess)) await self.async_set_unique_id(re.sub("\\..*$", "", data.remoteaccess))
return self.async_create_entry(title=data.gogogatename, data=user_input) return self.async_create_entry(title=title, data=user_input)
except ApiError as api_error: except ApiError as api_error:
if api_error.code in ( device_type = user_input[CONF_DEVICE]
ApiErrorCode.CREDENTIALS_NOT_SET, is_invalid_auth = (
ApiErrorCode.CREDENTIALS_INCORRECT, device_type == DEVICE_TYPE_GOGOGATE2
): and api_error.code
in (
GogoGate2ApiErrorCode.CREDENTIALS_NOT_SET,
GogoGate2ApiErrorCode.CREDENTIALS_INCORRECT,
)
) or (
device_type == DEVICE_TYPE_ISMARTGATE
and api_error.code
in (
ISmartGateApiErrorCode.CREDENTIALS_NOT_SET,
ISmartGateApiErrorCode.CREDENTIALS_INCORRECT,
)
)
if is_invalid_auth:
errors["base"] = "invalid_auth" errors["base"] = "invalid_auth"
else: else:
errors["base"] = "cannot_connect" errors["base"] = "cannot_connect"
@ -59,6 +86,10 @@ class Gogogate2FlowHandler(ConfigFlow, domain=DOMAIN):
step_id="user", step_id="user",
data_schema=vol.Schema( data_schema=vol.Schema(
{ {
vol.Required(
CONF_DEVICE,
default=user_input.get(CONF_DEVICE, DEVICE_TYPE_GOGOGATE2),
): vol.In((DEVICE_TYPE_GOGOGATE2, DEVICE_TYPE_ISMARTGATE)),
vol.Required( vol.Required(
CONF_IP_ADDRESS, default=user_input.get(CONF_IP_ADDRESS, "") CONF_IP_ADDRESS, default=user_input.get(CONF_IP_ADDRESS, "")
): str, ): str,

View File

@ -2,3 +2,5 @@
DOMAIN = "gogogate2" DOMAIN = "gogogate2"
DATA_UPDATE_COORDINATOR = "data_update_coordinator" DATA_UPDATE_COORDINATOR = "data_update_coordinator"
DEVICE_TYPE_GOGOGATE2 = "gogogate2"
DEVICE_TYPE_ISMARTGATE = "ismartgate"

View File

@ -2,7 +2,12 @@
import logging import logging
from typing import Callable, List, Optional from typing import Callable, List, Optional
from gogogate2_api.common import Door, DoorStatus, get_configured_doors, get_door_by_id from gogogate2_api.common import (
AbstractDoor,
DoorStatus,
get_configured_doors,
get_door_by_id,
)
import voluptuous as vol import voluptuous as vol
from homeassistant.components.cover import ( from homeassistant.components.cover import (
@ -12,17 +17,23 @@ from homeassistant.components.cover import (
CoverEntity, CoverEntity,
) )
from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry
from homeassistant.const import CONF_IP_ADDRESS, CONF_PASSWORD, CONF_USERNAME from homeassistant.const import (
from homeassistant.core import HomeAssistant, callback CONF_DEVICE,
CONF_IP_ADDRESS,
CONF_PASSWORD,
CONF_USERNAME,
)
from homeassistant.core import HomeAssistant
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity import Entity from homeassistant.helpers.entity import Entity
from homeassistant.helpers.update_coordinator import CoordinatorEntity
from .common import ( from .common import (
GogoGateDataUpdateCoordinator, DeviceDataUpdateCoordinator,
cover_unique_id, cover_unique_id,
get_data_update_coordinator, get_data_update_coordinator,
) )
from .const import DOMAIN from .const import DEVICE_TYPE_GOGOGATE2, DEVICE_TYPE_ISMARTGATE, DOMAIN
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -30,6 +41,9 @@ _LOGGER = logging.getLogger(__name__)
COVER_SCHEMA = vol.Schema( COVER_SCHEMA = vol.Schema(
{ {
vol.Required(CONF_IP_ADDRESS): cv.string, vol.Required(CONF_IP_ADDRESS): cv.string,
vol.Required(CONF_DEVICE, default=DEVICE_TYPE_GOGOGATE2): vol.In(
(DEVICE_TYPE_GOGOGATE2, DEVICE_TYPE_ISMARTGATE)
),
vol.Required(CONF_PASSWORD): cv.string, vol.Required(CONF_PASSWORD): cv.string,
vol.Required(CONF_USERNAME): cv.string, vol.Required(CONF_USERNAME): cv.string,
} }
@ -57,39 +71,29 @@ async def async_setup_entry(
async_add_entities( async_add_entities(
[ [
Gogogate2Cover(config_entry, data_update_coordinator, door) DeviceCover(config_entry, data_update_coordinator, door)
for door in get_configured_doors(data_update_coordinator.data) for door in get_configured_doors(data_update_coordinator.data)
] ]
) )
class Gogogate2Cover(CoverEntity): class DeviceCover(CoordinatorEntity, CoverEntity):
"""Cover entity for goggate2.""" """Cover entity for goggate2."""
def __init__( def __init__(
self, self,
config_entry: ConfigEntry, config_entry: ConfigEntry,
data_update_coordinator: GogoGateDataUpdateCoordinator, data_update_coordinator: DeviceDataUpdateCoordinator,
door: Door, door: AbstractDoor,
) -> None: ) -> None:
"""Initialize the object.""" """Initialize the object."""
super().__init__(data_update_coordinator)
self._config_entry = config_entry self._config_entry = config_entry
self._data_update_coordinator = data_update_coordinator
self._door = door self._door = door
self._api = data_update_coordinator.api self._api = data_update_coordinator.api
self._unique_id = cover_unique_id(config_entry, door) self._unique_id = cover_unique_id(config_entry, door)
self._is_available = True self._is_available = True
@property
def available(self) -> bool:
"""Return True if entity is available."""
return self._is_available
@property
def should_poll(self) -> bool:
"""Return False as the data manager handles dispatching data."""
return False
@property @property
def unique_id(self) -> Optional[str]: def unique_id(self) -> Optional[str]:
"""Return a unique ID.""" """Return a unique ID."""
@ -98,14 +102,16 @@ class Gogogate2Cover(CoverEntity):
@property @property
def name(self): def name(self):
"""Return the name of the door.""" """Return the name of the door."""
return self._door.name return self._get_door().name
@property @property
def is_closed(self): def is_closed(self):
"""Return true if cover is closed, else False.""" """Return true if cover is closed, else False."""
if self._door.status == DoorStatus.OPENED: door = self._get_door()
if door.status == DoorStatus.OPENED:
return False return False
if self._door.status == DoorStatus.CLOSED: if door.status == DoorStatus.CLOSED:
return True return True
return None return None
@ -122,36 +128,24 @@ class Gogogate2Cover(CoverEntity):
async def async_open_cover(self, **kwargs): async def async_open_cover(self, **kwargs):
"""Open the door.""" """Open the door."""
await self.hass.async_add_executor_job(self._api.open_door, self._door.door_id) await self.hass.async_add_executor_job(
self._api.open_door, self._get_door().door_id
)
async def async_close_cover(self, **kwargs): async def async_close_cover(self, **kwargs):
"""Close the door.""" """Close the door."""
await self.hass.async_add_executor_job(self._api.close_door, self._door.door_id) await self.hass.async_add_executor_job(
self._api.close_door, self._get_door().door_id
)
@property @property
def state_attributes(self): def state_attributes(self):
"""Return the state attributes.""" """Return the state attributes."""
attrs = super().state_attributes attrs = super().state_attributes
attrs["door_id"] = self._door.door_id attrs["door_id"] = self._get_door().door_id
return attrs return attrs
@callback def _get_door(self) -> AbstractDoor:
def async_on_data_updated(self) -> None: door = get_door_by_id(self._door.door_id, self.coordinator.data)
"""Receive data from data dispatcher.""" self._door = door or self._door
if not self._data_update_coordinator.last_update_success: return self._door
self._is_available = False
self.async_write_ha_state()
return
door = get_door_by_id(self._door.door_id, self._data_update_coordinator.data)
# Set the state.
self._door = door
self._is_available = True
self.async_write_ha_state()
async def async_added_to_hass(self) -> None:
"""Register update dispatcher."""
self.async_on_remove(
self._data_update_coordinator.async_add_listener(self.async_on_data_updated)
)

View File

@ -1,8 +1,8 @@
{ {
"domain": "gogogate2", "domain": "gogogate2",
"name": "Gogogate2", "name": "Gogogate2 and iSmartGate",
"config_flow": true, "config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/gogogate2", "documentation": "https://www.home-assistant.io/integrations/gogogate2",
"requirements": ["gogogate2-api==1.0.4"], "requirements": ["gogogate2-api==2.0.0"],
"codeowners": ["@vangorra"] "codeowners": ["@vangorra"]
} }

View File

@ -9,7 +9,7 @@
}, },
"step": { "step": {
"user": { "user": {
"title": "Setup GogoGate2", "title": "Setup GogoGate2 or iSmartGate",
"description": "Provide requisite information below.", "description": "Provide requisite information below.",
"data": { "data": {
"ip_address": "IP Address", "ip_address": "IP Address",

View File

@ -668,7 +668,7 @@ glances_api==0.2.0
gntp==1.0.3 gntp==1.0.3
# homeassistant.components.gogogate2 # homeassistant.components.gogogate2
gogogate2-api==1.0.4 gogogate2-api==2.0.0
# homeassistant.components.google # homeassistant.components.google
google-api-python-client==1.6.4 google-api-python-client==1.6.4

View File

@ -334,7 +334,7 @@ gios==0.1.4
glances_api==0.2.0 glances_api==0.2.0
# homeassistant.components.gogogate2 # homeassistant.components.gogogate2
gogogate2-api==1.0.4 gogogate2-api==2.0.0
# homeassistant.components.google # homeassistant.components.google
google-api-python-client==1.6.4 google-api-python-client==1.6.4

View File

@ -1,163 +0,0 @@
"""Common test code."""
from typing import List, NamedTuple, Optional
from unittest.mock import MagicMock, Mock
from gogogate2_api import GogoGate2Api, InfoResponse
from gogogate2_api.common import Door, DoorMode, DoorStatus, Network, Outputs, Wifi
from homeassistant.components import persistent_notification
from homeassistant.components.cover import DOMAIN as COVER_DOMAIN
from homeassistant.components.gogogate2 import async_unload_entry
from homeassistant.components.gogogate2.common import (
GogoGateDataUpdateCoordinator,
get_data_update_coordinator,
)
import homeassistant.components.gogogate2.const as const
from homeassistant.components.homeassistant import DOMAIN as HA_DOMAIN
from homeassistant.config import async_process_ha_core_config
from homeassistant.config_entries import SOURCE_USER
from homeassistant.const import CONF_UNIT_SYSTEM, CONF_UNIT_SYSTEM_METRIC
from homeassistant.core import HomeAssistant
from homeassistant.data_entry_flow import RESULT_TYPE_CREATE_ENTRY, RESULT_TYPE_FORM
from homeassistant.setup import async_setup_component
INFO_RESPONSE = InfoResponse(
user="user1",
gogogatename="gogogatename1",
model="",
apiversion="",
remoteaccessenabled=False,
remoteaccess="abcdefg.my-gogogate.com",
firmwareversion="",
apicode="API_CODE",
door1=Door(
door_id=1,
permission=True,
name="Door1",
mode=DoorMode.GARAGE,
status=DoorStatus.OPENED,
sensor=True,
sensorid=None,
camera=False,
events=2,
temperature=None,
),
door2=Door(
door_id=2,
permission=True,
name=None,
mode=DoorMode.GARAGE,
status=DoorStatus.OPENED,
sensor=True,
sensorid=None,
camera=False,
events=2,
temperature=None,
),
door3=Door(
door_id=3,
permission=True,
name="Door3",
mode=DoorMode.GARAGE,
status=DoorStatus.OPENED,
sensor=True,
sensorid=None,
camera=False,
events=2,
temperature=None,
),
outputs=Outputs(output1=True, output2=False, output3=True),
network=Network(ip=""),
wifi=Wifi(SSID="", linkquality="", signal=""),
)
class ComponentData(NamedTuple):
"""Test data for a mocked component."""
api: GogoGate2Api
data_update_coordinator: GogoGateDataUpdateCoordinator
class ComponentFactory:
"""Manages the setup and unloading of the withing component and profiles."""
def __init__(self, hass: HomeAssistant, gogogate_api_mock: Mock) -> None:
"""Initialize the object."""
self._hass = hass
self._gogogate_api_mock = gogogate_api_mock
@property
def api_class_mock(self):
"""Get the api class mock."""
return self._gogogate_api_mock
async def configure_component(
self, cover_config: Optional[List[dict]] = None
) -> None:
"""Configure the component."""
hass_config = {
"homeassistant": {CONF_UNIT_SYSTEM: CONF_UNIT_SYSTEM_METRIC},
"cover": cover_config or [],
}
await async_process_ha_core_config(self._hass, hass_config.get("homeassistant"))
assert await async_setup_component(self._hass, HA_DOMAIN, {})
assert await async_setup_component(
self._hass, persistent_notification.DOMAIN, {}
)
assert await async_setup_component(self._hass, COVER_DOMAIN, hass_config)
assert await async_setup_component(self._hass, const.DOMAIN, hass_config)
await self._hass.async_block_till_done()
async def run_config_flow(
self, config_data: dict, api_mock: Optional[GogoGate2Api] = None
) -> ComponentData:
"""Run a config flow."""
if api_mock is None:
api_mock: GogoGate2Api = MagicMock(spec=GogoGate2Api)
api_mock.info.return_value = INFO_RESPONSE
self._gogogate_api_mock.reset_mocks()
self._gogogate_api_mock.return_value = api_mock
result = await self._hass.config_entries.flow.async_init(
const.DOMAIN, context={"source": SOURCE_USER}
)
assert result
assert result["type"] == RESULT_TYPE_FORM
assert result["step_id"] == "user"
result = await self._hass.config_entries.flow.async_configure(
result["flow_id"],
user_input=config_data,
)
assert result
assert result["type"] == RESULT_TYPE_CREATE_ENTRY
assert result["data"] == config_data
await self._hass.async_block_till_done()
config_entry = next(
iter(
entry
for entry in self._hass.config_entries.async_entries(const.DOMAIN)
if entry.unique_id == "abcdefg"
)
)
return ComponentData(
api=api_mock,
data_update_coordinator=get_data_update_coordinator(
self._hass, config_entry
),
)
async def unload(self) -> None:
"""Unload all config entries."""
config_entries = self._hass.config_entries.async_entries(const.DOMAIN)
for config_entry in config_entries:
await async_unload_entry(self._hass, config_entry)
await self._hass.async_block_till_done()
assert not self._hass.states.async_entity_ids("gogogate")

View File

@ -1,18 +0,0 @@
"""Fixtures for tests."""
import pytest
from homeassistant.core import HomeAssistant
from .common import ComponentFactory
from tests.async_mock import patch
@pytest.fixture()
def component_factory(hass: HomeAssistant):
"""Return a factory for initializing the gogogate2 api."""
with patch(
"homeassistant.components.gogogate2.common.GogoGate2Api"
) as gogogate2_api_mock:
yield ComponentFactory(hass, gogogate2_api_mock)

View File

@ -1,65 +1,66 @@
"""Tests for the GogoGate2 component.""" """Tests for the GogoGate2 component."""
from gogogate2_api import GogoGate2Api from gogogate2_api import GogoGate2Api
from gogogate2_api.common import ApiError from gogogate2_api.common import ApiError
from gogogate2_api.const import ApiErrorCode from gogogate2_api.const import GogoGate2ApiErrorCode
from homeassistant.components.gogogate2.const import DEVICE_TYPE_GOGOGATE2
from homeassistant.config_entries import SOURCE_USER from homeassistant.config_entries import SOURCE_USER
from homeassistant.const import CONF_IP_ADDRESS, CONF_PASSWORD, CONF_USERNAME from homeassistant.const import (
CONF_DEVICE,
CONF_IP_ADDRESS,
CONF_PASSWORD,
CONF_USERNAME,
)
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.data_entry_flow import RESULT_TYPE_FORM from homeassistant.data_entry_flow import RESULT_TYPE_FORM
from .common import ComponentFactory
from tests.async_mock import MagicMock, patch from tests.async_mock import MagicMock, patch
@patch("homeassistant.components.gogogate2.async_setup", return_value=True)
@patch("homeassistant.components.gogogate2.async_setup_entry", return_value=True)
@patch("homeassistant.components.gogogate2.common.GogoGate2Api")
async def test_auth_fail( async def test_auth_fail(
hass: HomeAssistant, component_factory: ComponentFactory gogogate2api_mock, async_setup_entry_mock, async_setup_mock, hass: HomeAssistant
) -> None: ) -> None:
"""Test authorization failures.""" """Test authorization failures."""
api_mock: GogoGate2Api = MagicMock(spec=GogoGate2Api) api: GogoGate2Api = MagicMock(spec=GogoGate2Api)
gogogate2api_mock.return_value = api
with patch( api.reset_mock()
"homeassistant.components.gogogate2.async_setup", return_value=True api.info.side_effect = ApiError(GogoGate2ApiErrorCode.CREDENTIALS_INCORRECT, "blah")
), patch( result = await hass.config_entries.flow.async_init(
"homeassistant.components.gogogate2.async_setup_entry", "gogogate2", context={"source": SOURCE_USER}
return_value=True, )
): result = await hass.config_entries.flow.async_configure(
await component_factory.configure_component() result["flow_id"],
component_factory.api_class_mock.return_value = api_mock user_input={
CONF_DEVICE: DEVICE_TYPE_GOGOGATE2,
CONF_IP_ADDRESS: "127.0.0.2",
CONF_USERNAME: "user0",
CONF_PASSWORD: "password0",
},
)
assert result
assert result["type"] == RESULT_TYPE_FORM
assert result["errors"] == {
"base": "invalid_auth",
}
api_mock.reset_mock() api.reset_mock()
api_mock.info.side_effect = ApiError(ApiErrorCode.CREDENTIALS_INCORRECT, "blah") api.info.side_effect = Exception("Generic connection error.")
result = await hass.config_entries.flow.async_init( result = await hass.config_entries.flow.async_init(
"gogogate2", context={"source": SOURCE_USER} "gogogate2", context={"source": SOURCE_USER}
) )
result = await hass.config_entries.flow.async_configure( result = await hass.config_entries.flow.async_configure(
result["flow_id"], result["flow_id"],
user_input={ user_input={
CONF_IP_ADDRESS: "127.0.0.2", CONF_DEVICE: DEVICE_TYPE_GOGOGATE2,
CONF_USERNAME: "user0", CONF_IP_ADDRESS: "127.0.0.2",
CONF_PASSWORD: "password0", CONF_USERNAME: "user0",
}, CONF_PASSWORD: "password0",
) },
assert result )
assert result["type"] == RESULT_TYPE_FORM assert result
assert result["errors"] == { assert result["type"] == RESULT_TYPE_FORM
"base": "invalid_auth", assert result["errors"] == {"base": "cannot_connect"}
}
api_mock.reset_mock()
api_mock.info.side_effect = Exception("Generic connection error.")
result = await hass.config_entries.flow.async_init(
"gogogate2", context={"source": SOURCE_USER}
)
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
user_input={
CONF_IP_ADDRESS: "127.0.0.2",
CONF_USERNAME: "user0",
CONF_PASSWORD: "password0",
},
)
assert result
assert result["type"] == RESULT_TYPE_FORM
assert result["errors"] == {"base": "cannot_connect"}

View File

@ -1,65 +1,90 @@
"""Tests for the GogoGate2 component.""" """Tests for the GogoGate2 component."""
from gogogate2_api import GogoGate2Api from datetime import timedelta
from gogogate2_api import GogoGate2Api, ISmartGateApi
from gogogate2_api.common import ( from gogogate2_api.common import (
ActivateResponse,
ApiError, ApiError,
Door,
DoorMode, DoorMode,
DoorStatus, DoorStatus,
InfoResponse, GogoGate2ActivateResponse,
GogoGate2Door,
GogoGate2InfoResponse,
ISmartGateDoor,
ISmartGateInfoResponse,
Network, Network,
Outputs, Outputs,
Wifi, Wifi,
) )
from homeassistant.components.cover import DOMAIN as COVER_DOMAIN from homeassistant.components.cover import DOMAIN as COVER_DOMAIN
from homeassistant.components.gogogate2.const import (
DEVICE_TYPE_GOGOGATE2,
DEVICE_TYPE_ISMARTGATE,
DOMAIN,
)
from homeassistant.components.homeassistant import DOMAIN as HA_DOMAIN from homeassistant.components.homeassistant import DOMAIN as HA_DOMAIN
from homeassistant.config import async_process_ha_core_config
from homeassistant.config_entries import SOURCE_USER
from homeassistant.const import ( from homeassistant.const import (
CONF_DEVICE,
CONF_IP_ADDRESS, CONF_IP_ADDRESS,
CONF_NAME, CONF_NAME,
CONF_PASSWORD, CONF_PASSWORD,
CONF_PLATFORM, CONF_PLATFORM,
CONF_UNIT_SYSTEM,
CONF_UNIT_SYSTEM_METRIC,
CONF_USERNAME, CONF_USERNAME,
STATE_CLOSED, STATE_CLOSED,
STATE_OPEN, STATE_OPEN,
STATE_UNAVAILABLE, STATE_UNAVAILABLE,
STATE_UNKNOWN,
) )
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.setup import async_setup_component
from homeassistant.util.dt import utcnow
from .common import ComponentFactory from tests.async_mock import MagicMock, patch
from tests.common import MockConfigEntry, async_fire_time_changed
from tests.async_mock import MagicMock
async def test_import_fail( @patch("homeassistant.components.gogogate2.common.GogoGate2Api")
hass: HomeAssistant, component_factory: ComponentFactory async def test_import_fail(gogogate2api_mock, hass: HomeAssistant) -> None:
) -> None:
"""Test the failure to import.""" """Test the failure to import."""
api = MagicMock(spec=GogoGate2Api) api = MagicMock(spec=GogoGate2Api)
api.info.side_effect = ApiError(22, "Error") api.info.side_effect = ApiError(22, "Error")
gogogate2api_mock.return_value = api
component_factory.api_class_mock.return_value = api hass_config = {
HA_DOMAIN: {CONF_UNIT_SYSTEM: CONF_UNIT_SYSTEM_METRIC},
await component_factory.configure_component( COVER_DOMAIN: [
cover_config=[
{ {
CONF_PLATFORM: "gogogate2", CONF_PLATFORM: "gogogate2",
CONF_NAME: "cover0", CONF_NAME: "cover0",
CONF_DEVICE: DEVICE_TYPE_GOGOGATE2,
CONF_IP_ADDRESS: "127.0.1.0", CONF_IP_ADDRESS: "127.0.1.0",
CONF_USERNAME: "user0", CONF_USERNAME: "user0",
CONF_PASSWORD: "password0", CONF_PASSWORD: "password0",
} }
] ],
) }
await async_process_ha_core_config(hass, hass_config[HA_DOMAIN])
assert await async_setup_component(hass, HA_DOMAIN, {})
assert await async_setup_component(hass, COVER_DOMAIN, hass_config)
await hass.async_block_till_done()
entity_ids = hass.states.async_entity_ids(COVER_DOMAIN) entity_ids = hass.states.async_entity_ids(COVER_DOMAIN)
assert not entity_ids assert not entity_ids
async def test_import(hass: HomeAssistant, component_factory: ComponentFactory) -> None: @patch("homeassistant.components.gogogate2.common.GogoGate2Api")
@patch("homeassistant.components.gogogate2.common.ISmartGateApi")
async def test_import(
ismartgateapi_mock, gogogate2api_mock, hass: HomeAssistant
) -> None:
"""Test importing of file based config.""" """Test importing of file based config."""
api0 = MagicMock(spec=GogoGate2Api) api0 = MagicMock(spec=GogoGate2Api)
api0.info.return_value = InfoResponse( api0.info.return_value = GogoGate2InfoResponse(
user="user1", user="user1",
gogogatename="gogogatename0", gogogatename="gogogatename0",
model="", model="",
@ -68,7 +93,7 @@ async def test_import(hass: HomeAssistant, component_factory: ComponentFactory)
remoteaccess="abc123.blah.blah", remoteaccess="abc123.blah.blah",
firmwareversion="", firmwareversion="",
apicode="", apicode="",
door1=Door( door1=GogoGate2Door(
door_id=1, door_id=1,
permission=True, permission=True,
name="Door1", name="Door1",
@ -80,7 +105,7 @@ async def test_import(hass: HomeAssistant, component_factory: ComponentFactory)
events=2, events=2,
temperature=None, temperature=None,
), ),
door2=Door( door2=GogoGate2Door(
door_id=2, door_id=2,
permission=True, permission=True,
name=None, name=None,
@ -92,7 +117,7 @@ async def test_import(hass: HomeAssistant, component_factory: ComponentFactory)
events=0, events=0,
temperature=None, temperature=None,
), ),
door3=Door( door3=GogoGate2Door(
door_id=3, door_id=3,
permission=True, permission=True,
name=None, name=None,
@ -108,18 +133,21 @@ async def test_import(hass: HomeAssistant, component_factory: ComponentFactory)
network=Network(ip=""), network=Network(ip=""),
wifi=Wifi(SSID="", linkquality="", signal=""), wifi=Wifi(SSID="", linkquality="", signal=""),
) )
gogogate2api_mock.return_value = api0
api1 = MagicMock(spec=GogoGate2Api) api1 = MagicMock(spec=ISmartGateApi)
api1.info.return_value = InfoResponse( api1.info.return_value = ISmartGateInfoResponse(
user="user1", user="user1",
gogogatename="gogogatename0", ismartgatename="ismartgatename0",
model="", model="",
apiversion="", apiversion="",
remoteaccessenabled=False, remoteaccessenabled=False,
remoteaccess="321bca.blah.blah", remoteaccess="abc321.blah.blah",
firmwareversion="", firmwareversion="",
apicode="", pin=123,
door1=Door( lang="en",
newfirmware=False,
door1=ISmartGateDoor(
door_id=1, door_id=1,
permission=True, permission=True,
name="Door1", name="Door1",
@ -130,50 +158,52 @@ async def test_import(hass: HomeAssistant, component_factory: ComponentFactory)
camera=False, camera=False,
events=2, events=2,
temperature=None, temperature=None,
enabled=True,
apicode="apicode0",
customimage=False,
), ),
door2=Door( door2=ISmartGateDoor(
door_id=2, door_id=1,
permission=True, permission=True,
name=None, name=None,
mode=DoorMode.GARAGE, mode=DoorMode.GARAGE,
status=DoorStatus.UNDEFINED, status=DoorStatus.CLOSED,
sensor=True, sensor=True,
sensorid=None, sensorid=None,
camera=False, camera=False,
events=0, events=2,
temperature=None, temperature=None,
enabled=True,
apicode="apicode0",
customimage=False,
), ),
door3=Door( door3=ISmartGateDoor(
door_id=3, door_id=1,
permission=True, permission=True,
name=None, name=None,
mode=DoorMode.GARAGE, mode=DoorMode.GARAGE,
status=DoorStatus.UNDEFINED, status=DoorStatus.CLOSED,
sensor=True, sensor=True,
sensorid=None, sensorid=None,
camera=False, camera=False,
events=0, events=2,
temperature=None, temperature=None,
enabled=True,
apicode="apicode0",
customimage=False,
), ),
outputs=Outputs(output1=True, output2=False, output3=True),
network=Network(ip=""), network=Network(ip=""),
wifi=Wifi(SSID="", linkquality="", signal=""), wifi=Wifi(SSID="", linkquality="", signal=""),
) )
ismartgateapi_mock.return_value = api1
def new_api(ip_address: str, username: str, password: str) -> GogoGate2Api: hass_config = {
if ip_address == "127.0.1.0": HA_DOMAIN: {CONF_UNIT_SYSTEM: CONF_UNIT_SYSTEM_METRIC},
return api0 COVER_DOMAIN: [
if ip_address == "127.0.1.1":
return api1
raise Exception(f"Untested ip address {ip_address}")
component_factory.api_class_mock.side_effect = new_api
await component_factory.configure_component(
cover_config=[
{ {
CONF_PLATFORM: "gogogate2", CONF_PLATFORM: "gogogate2",
CONF_NAME: "cover0", CONF_NAME: "cover0",
CONF_DEVICE: DEVICE_TYPE_GOGOGATE2,
CONF_IP_ADDRESS: "127.0.1.0", CONF_IP_ADDRESS: "127.0.1.0",
CONF_USERNAME: "user0", CONF_USERNAME: "user0",
CONF_PASSWORD: "password0", CONF_PASSWORD: "password0",
@ -181,274 +211,148 @@ async def test_import(hass: HomeAssistant, component_factory: ComponentFactory)
{ {
CONF_PLATFORM: "gogogate2", CONF_PLATFORM: "gogogate2",
CONF_NAME: "cover1", CONF_NAME: "cover1",
CONF_DEVICE: DEVICE_TYPE_ISMARTGATE,
CONF_IP_ADDRESS: "127.0.1.1", CONF_IP_ADDRESS: "127.0.1.1",
CONF_USERNAME: "user1", CONF_USERNAME: "user1",
CONF_PASSWORD: "password1", CONF_PASSWORD: "password1",
}, },
] ],
) }
await async_process_ha_core_config(hass, hass_config[HA_DOMAIN])
assert await async_setup_component(hass, HA_DOMAIN, {})
assert await async_setup_component(hass, COVER_DOMAIN, hass_config)
await hass.async_block_till_done()
entity_ids = hass.states.async_entity_ids(COVER_DOMAIN) entity_ids = hass.states.async_entity_ids(COVER_DOMAIN)
assert entity_ids is not None assert entity_ids is not None
assert len(entity_ids) == 2 assert len(entity_ids) == 2
assert "cover.door1" in entity_ids assert "cover.door1" in entity_ids
assert "cover.door1_2" in entity_ids assert "cover.door1_2" in entity_ids
await component_factory.unload()
@patch("homeassistant.components.gogogate2.common.GogoGate2Api")
async def test_open_close_update(gogogat2api_mock, hass: HomeAssistant) -> None:
"""Test open and close and data update."""
async def test_cover_update( def info_response(door_status: DoorStatus) -> GogoGate2InfoResponse:
hass: HomeAssistant, component_factory: ComponentFactory return GogoGate2InfoResponse(
) -> None: user="user1",
"""Test cover.""" gogogatename="gogogatename0",
await component_factory.configure_component() model="",
component_data = await component_factory.run_config_flow( apiversion="",
config_data={ remoteaccessenabled=False,
CONF_IP_ADDRESS: "127.0.0.2", remoteaccess="abc123.blah.blah",
CONF_USERNAME: "user0", firmwareversion="",
CONF_PASSWORD: "password0", apicode="",
} door1=GogoGate2Door(
door_id=1,
permission=True,
name="Door1",
mode=DoorMode.GARAGE,
status=door_status,
sensor=True,
sensorid=None,
camera=False,
events=2,
temperature=None,
),
door2=GogoGate2Door(
door_id=2,
permission=True,
name=None,
mode=DoorMode.GARAGE,
status=DoorStatus.UNDEFINED,
sensor=True,
sensorid=None,
camera=False,
events=0,
temperature=None,
),
door3=GogoGate2Door(
door_id=3,
permission=True,
name=None,
mode=DoorMode.GARAGE,
status=DoorStatus.UNDEFINED,
sensor=True,
sensorid=None,
camera=False,
events=0,
temperature=None,
),
outputs=Outputs(output1=True, output2=False, output3=True),
network=Network(ip=""),
wifi=Wifi(SSID="", linkquality="", signal=""),
)
api = MagicMock(GogoGate2Api)
api.activate.return_value = GogoGate2ActivateResponse(result=True)
api.info.return_value = info_response(DoorStatus.OPENED)
gogogat2api_mock.return_value = api
config_entry = MockConfigEntry(
domain=DOMAIN,
source=SOURCE_USER,
data={
CONF_IP_ADDRESS: "127.0.0.1",
CONF_USERNAME: "admin",
CONF_PASSWORD: "password",
},
) )
config_entry.add_to_hass(hass)
assert hass.states.async_entity_ids(COVER_DOMAIN)
state = hass.states.get("cover.door1")
assert state
assert state.state == STATE_OPEN
assert state.attributes["friendly_name"] == "Door1"
assert state.attributes["supported_features"] == 3
assert state.attributes["device_class"] == "garage"
component_data.data_update_coordinator.api.info.return_value = InfoResponse(
user="user1",
gogogatename="gogogatename0",
model="",
apiversion="",
remoteaccessenabled=False,
remoteaccess="abc123.blah.blah",
firmwareversion="",
apicode="",
door1=Door(
door_id=1,
permission=True,
name="Door1",
mode=DoorMode.GARAGE,
status=DoorStatus.OPENED,
sensor=True,
sensorid=None,
camera=False,
events=2,
temperature=None,
),
door2=Door(
door_id=2,
permission=True,
name=None,
mode=DoorMode.GARAGE,
status=DoorStatus.UNDEFINED,
sensor=True,
sensorid=None,
camera=False,
events=0,
temperature=None,
),
door3=Door(
door_id=3,
permission=True,
name=None,
mode=DoorMode.GARAGE,
status=DoorStatus.UNDEFINED,
sensor=True,
sensorid=None,
camera=False,
events=0,
temperature=None,
),
outputs=Outputs(output1=True, output2=False, output3=True),
network=Network(ip=""),
wifi=Wifi(SSID="", linkquality="", signal=""),
)
await component_data.data_update_coordinator.async_refresh()
await hass.async_block_till_done()
state = hass.states.get("cover.door1")
assert state
assert state.state == STATE_OPEN
component_data.data_update_coordinator.api.info.return_value = InfoResponse(
user="user1",
gogogatename="gogogatename0",
model="",
apiversion="",
remoteaccessenabled=False,
remoteaccess="abc123.blah.blah",
firmwareversion="",
apicode="",
door1=Door(
door_id=1,
permission=True,
name="Door1",
mode=DoorMode.GARAGE,
status=DoorStatus.CLOSED,
sensor=True,
sensorid=None,
camera=False,
events=2,
temperature=None,
),
door2=Door(
door_id=2,
permission=True,
name=None,
mode=DoorMode.GARAGE,
status=DoorStatus.UNDEFINED,
sensor=True,
sensorid=None,
camera=False,
events=0,
temperature=None,
),
door3=Door(
door_id=3,
permission=True,
name=None,
mode=DoorMode.GARAGE,
status=DoorStatus.UNDEFINED,
sensor=True,
sensorid=None,
camera=False,
events=0,
temperature=None,
),
outputs=Outputs(output1=True, output2=False, output3=True),
network=Network(ip=""),
wifi=Wifi(SSID="", linkquality="", signal=""),
)
await component_data.data_update_coordinator.async_refresh()
await hass.async_block_till_done()
state = hass.states.get("cover.door1")
assert state
assert state.state == STATE_CLOSED
async def test_open_close(
hass: HomeAssistant, component_factory: ComponentFactory
) -> None:
"""Test open and close."""
closed_door_response = InfoResponse(
user="user1",
gogogatename="gogogatename0",
model="",
apiversion="",
remoteaccessenabled=False,
remoteaccess="abc123.blah.blah",
firmwareversion="",
apicode="",
door1=Door(
door_id=1,
permission=True,
name="Door1",
mode=DoorMode.GARAGE,
status=DoorStatus.CLOSED,
sensor=True,
sensorid=None,
camera=False,
events=2,
temperature=None,
),
door2=Door(
door_id=2,
permission=True,
name=None,
mode=DoorMode.GARAGE,
status=DoorStatus.UNDEFINED,
sensor=True,
sensorid=None,
camera=False,
events=0,
temperature=None,
),
door3=Door(
door_id=3,
permission=True,
name=None,
mode=DoorMode.GARAGE,
status=DoorStatus.UNDEFINED,
sensor=True,
sensorid=None,
camera=False,
events=0,
temperature=None,
),
outputs=Outputs(output1=True, output2=False, output3=True),
network=Network(ip=""),
wifi=Wifi(SSID="", linkquality="", signal=""),
)
await component_factory.configure_component()
assert hass.states.get("cover.door1") is None assert hass.states.get("cover.door1") is None
assert await hass.config_entries.async_setup(config_entry.entry_id)
component_data = await component_factory.run_config_flow( await hass.async_block_till_done()
config_data={
CONF_IP_ADDRESS: "127.0.0.2",
CONF_USERNAME: "user0",
CONF_PASSWORD: "password0",
}
)
component_data.api.activate.return_value = ActivateResponse(result=True)
assert hass.states.get("cover.door1").state == STATE_OPEN assert hass.states.get("cover.door1").state == STATE_OPEN
api.info.return_value = info_response(DoorStatus.CLOSED)
await hass.services.async_call( await hass.services.async_call(
COVER_DOMAIN, COVER_DOMAIN,
"close_cover", "close_cover",
service_data={"entity_id": "cover.door1"}, service_data={"entity_id": "cover.door1"},
) )
await hass.async_block_till_done() async_fire_time_changed(hass, utcnow() + timedelta(hours=2))
component_data.api.close_door.assert_called_with(1)
component_data.data_update_coordinator.api.info.return_value = closed_door_response
await component_data.data_update_coordinator.async_refresh()
await hass.async_block_till_done() await hass.async_block_till_done()
assert hass.states.get("cover.door1").state == STATE_CLOSED assert hass.states.get("cover.door1").state == STATE_CLOSED
api.close_door.assert_called_with(1)
# Assert mid state changed when new status is received. api.info.return_value = info_response(DoorStatus.OPENED)
await hass.services.async_call( await hass.services.async_call(
COVER_DOMAIN, COVER_DOMAIN,
"open_cover", "open_cover",
service_data={"entity_id": "cover.door1"}, service_data={"entity_id": "cover.door1"},
) )
async_fire_time_changed(hass, utcnow() + timedelta(hours=2))
await hass.async_block_till_done() await hass.async_block_till_done()
component_data.api.open_door.assert_called_with(1) assert hass.states.get("cover.door1").state == STATE_OPEN
api.open_door.assert_called_with(1)
# Assert the mid state does not change when the same status is returned. api.info.return_value = info_response(DoorStatus.UNDEFINED)
component_data.data_update_coordinator.api.info.return_value = closed_door_response async_fire_time_changed(hass, utcnow() + timedelta(hours=2))
await component_data.data_update_coordinator.async_refresh()
component_data.data_update_coordinator.api.info.return_value = closed_door_response
await component_data.data_update_coordinator.async_refresh()
await component_data.data_update_coordinator.async_refresh()
await hass.services.async_call(
HA_DOMAIN,
"update_entity",
service_data={"entity_id": "cover.door1"},
)
await hass.async_block_till_done() await hass.async_block_till_done()
assert hass.states.get("cover.door1").state == STATE_CLOSED assert hass.states.get("cover.door1").state == STATE_UNKNOWN
assert await hass.config_entries.async_unload(config_entry.entry_id)
assert not hass.states.async_entity_ids(DOMAIN)
async def test_availability( @patch("homeassistant.components.gogogate2.common.ISmartGateApi")
hass: HomeAssistant, component_factory: ComponentFactory async def test_availability(ismartgateapi_mock, hass: HomeAssistant) -> None:
) -> None: """Test availability."""
"""Test open and close.""" closed_door_response = ISmartGateInfoResponse(
closed_door_response = InfoResponse(
user="user1", user="user1",
gogogatename="gogogatename0", ismartgatename="ismartgatename0",
model="", model="",
apiversion="", apiversion="",
remoteaccessenabled=False, remoteaccessenabled=False,
remoteaccess="abc123.blah.blah", remoteaccess="abc123.blah.blah",
firmwareversion="", firmwareversion="",
apicode="", pin=123,
door1=Door( lang="en",
newfirmware=False,
door1=ISmartGateDoor(
door_id=1, door_id=1,
permission=True, permission=True,
name="Door1", name="Door1",
@ -459,8 +363,11 @@ async def test_availability(
camera=False, camera=False,
events=2, events=2,
temperature=None, temperature=None,
enabled=True,
apicode="apicode0",
customimage=False,
), ),
door2=Door( door2=ISmartGateDoor(
door_id=2, door_id=2,
permission=True, permission=True,
name=None, name=None,
@ -471,8 +378,11 @@ async def test_availability(
camera=False, camera=False,
events=0, events=0,
temperature=None, temperature=None,
enabled=True,
apicode="apicode0",
customimage=False,
), ),
door3=Door( door3=ISmartGateDoor(
door_id=3, door_id=3,
permission=True, permission=True,
name=None, name=None,
@ -483,31 +393,43 @@ async def test_availability(
camera=False, camera=False,
events=0, events=0,
temperature=None, temperature=None,
enabled=True,
apicode="apicode0",
customimage=False,
), ),
outputs=Outputs(output1=True, output2=False, output3=True),
network=Network(ip=""), network=Network(ip=""),
wifi=Wifi(SSID="", linkquality="", signal=""), wifi=Wifi(SSID="", linkquality="", signal=""),
) )
await component_factory.configure_component() api = MagicMock(ISmartGateApi)
assert hass.states.get("cover.door1") is None api.info.return_value = closed_door_response
ismartgateapi_mock.return_value = api
component_data = await component_factory.run_config_flow( config_entry = MockConfigEntry(
config_data={ domain=DOMAIN,
CONF_IP_ADDRESS: "127.0.0.2", source=SOURCE_USER,
CONF_USERNAME: "user0", data={
CONF_PASSWORD: "password0", CONF_DEVICE: DEVICE_TYPE_ISMARTGATE,
} CONF_IP_ADDRESS: "127.0.0.1",
CONF_USERNAME: "admin",
CONF_PASSWORD: "password",
},
) )
assert hass.states.get("cover.door1").state == STATE_OPEN config_entry.add_to_hass(hass)
component_data.api.info.side_effect = Exception("Error") assert hass.states.get("cover.door1") is None
await component_data.data_update_coordinator.async_refresh() assert await hass.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done()
assert hass.states.get("cover.door1")
api.info.side_effect = Exception("Error")
async_fire_time_changed(hass, utcnow() + timedelta(hours=2))
await hass.async_block_till_done() await hass.async_block_till_done()
assert hass.states.get("cover.door1").state == STATE_UNAVAILABLE assert hass.states.get("cover.door1").state == STATE_UNAVAILABLE
component_data.api.info.side_effect = None api.info.side_effect = None
component_data.api.info.return_value = closed_door_response api.info.return_value = closed_door_response
await component_data.data_update_coordinator.async_refresh() async_fire_time_changed(hass, utcnow() + timedelta(hours=2))
await hass.async_block_till_done() await hass.async_block_till_done()
assert hass.states.get("cover.door1").state == STATE_CLOSED assert hass.states.get("cover.door1").state == STATE_CLOSED

View File

@ -1,8 +1,17 @@
"""Tests for the GogoGate2 component.""" """Tests for the GogoGate2 component."""
from gogogate2_api import GogoGate2Api
import pytest import pytest
from homeassistant.components.gogogate2 import async_setup_entry from homeassistant.components.gogogate2 import DEVICE_TYPE_GOGOGATE2, async_setup_entry
from homeassistant.components.gogogate2.common import GogoGateDataUpdateCoordinator from homeassistant.components.gogogate2.common import DeviceDataUpdateCoordinator
from homeassistant.components.gogogate2.const import DEVICE_TYPE_ISMARTGATE, DOMAIN
from homeassistant.config_entries import SOURCE_USER
from homeassistant.const import (
CONF_DEVICE,
CONF_IP_ADDRESS,
CONF_PASSWORD,
CONF_USERNAME,
)
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.exceptions import ConfigEntryNotReady
@ -10,11 +19,69 @@ from tests.async_mock import MagicMock, patch
from tests.common import MockConfigEntry from tests.common import MockConfigEntry
@patch("homeassistant.components.gogogate2.common.GogoGate2Api")
async def test_config_update(gogogate2api_mock, hass: HomeAssistant) -> None:
"""Test config setup where the config is updated."""
api = MagicMock(GogoGate2Api)
api.info.side_effect = Exception("Error")
gogogate2api_mock.return_value = api
config_entry = MockConfigEntry(
domain=DOMAIN,
source=SOURCE_USER,
data={
CONF_IP_ADDRESS: "127.0.0.1",
CONF_USERNAME: "admin",
CONF_PASSWORD: "password",
},
)
config_entry.add_to_hass(hass)
assert not await hass.config_entries.async_setup(entry_id=config_entry.entry_id)
await hass.async_block_till_done()
assert config_entry.data == {
CONF_DEVICE: DEVICE_TYPE_GOGOGATE2,
CONF_IP_ADDRESS: "127.0.0.1",
CONF_USERNAME: "admin",
CONF_PASSWORD: "password",
}
@patch("homeassistant.components.gogogate2.common.ISmartGateApi")
async def test_config_no_update(ismartgateapi_mock, hass: HomeAssistant) -> None:
"""Test config setup where the data is not updated."""
api = MagicMock(GogoGate2Api)
api.info.side_effect = Exception("Error")
ismartgateapi_mock.return_value = api
config_entry = MockConfigEntry(
domain=DOMAIN,
source=SOURCE_USER,
data={
CONF_DEVICE: DEVICE_TYPE_ISMARTGATE,
CONF_IP_ADDRESS: "127.0.0.1",
CONF_USERNAME: "admin",
CONF_PASSWORD: "password",
},
)
config_entry.add_to_hass(hass)
assert not await hass.config_entries.async_setup(entry_id=config_entry.entry_id)
await hass.async_block_till_done()
assert config_entry.data == {
CONF_DEVICE: DEVICE_TYPE_ISMARTGATE,
CONF_IP_ADDRESS: "127.0.0.1",
CONF_USERNAME: "admin",
CONF_PASSWORD: "password",
}
async def test_auth_fail(hass: HomeAssistant) -> None: async def test_auth_fail(hass: HomeAssistant) -> None:
"""Test authorization failures.""" """Test authorization failures."""
coordinator_mock: GogoGateDataUpdateCoordinator = MagicMock( coordinator_mock: DeviceDataUpdateCoordinator = MagicMock(
spec=GogoGateDataUpdateCoordinator spec=DeviceDataUpdateCoordinator
) )
coordinator_mock.last_update_success = False coordinator_mock.last_update_success = False