Refactor sharkiq tests (#39564)
* Refactor sharkiq tests * Fix linting * Remove unussed logger * Test one more code branch * Don't patch integration files * Remove legacy calls * Linting fixes * Refactor coordinator update tests * Reformat test params * Refector config flow tests * Minor code cleanup * Fix spelling error * Address review * Minor formatting change * Remove vacuum.py from .coveragerc
This commit is contained in:
parent
1cb60dd5c7
commit
01bac9f433
6 changed files with 287 additions and 344 deletions
|
@ -752,7 +752,6 @@ omit =
|
|||
homeassistant/components/sesame/lock.py
|
||||
homeassistant/components/seven_segments/image_processing.py
|
||||
homeassistant/components/seventeentrack/sensor.py
|
||||
homeassistant/components/sharkiq/vacuum.py
|
||||
homeassistant/components/shiftr/*
|
||||
homeassistant/components/shodan/sensor.py
|
||||
homeassistant/components/shelly/__init__.py
|
||||
|
|
|
@ -60,7 +60,7 @@ async def async_setup_entry(hass, config_entry):
|
|||
|
||||
shark_vacs = await ayla_api.async_get_devices(False)
|
||||
device_names = ", ".join([d.name for d in shark_vacs])
|
||||
LOGGER.debug("Found %d Shark IQ device(s): %s", len(device_names), device_names)
|
||||
LOGGER.debug("Found %d Shark IQ device(s): %s", len(shark_vacs), device_names)
|
||||
coordinator = SharkIqUpdateCoordinator(hass, config_entry, ayla_api, shark_vacs)
|
||||
|
||||
await coordinator.async_refresh()
|
||||
|
|
|
@ -71,3 +71,4 @@ TEST_USERNAME = "test-username"
|
|||
TEST_PASSWORD = "test-password"
|
||||
UNIQUE_ID = "foo@bar.com"
|
||||
CONFIG = {CONF_USERNAME: TEST_USERNAME, CONF_PASSWORD: TEST_PASSWORD}
|
||||
ENTRY_ID = "0123456789abcdef0123456789abcdef"
|
||||
|
|
|
@ -1,24 +1,18 @@
|
|||
"""Test the Shark IQ config flow."""
|
||||
import aiohttp
|
||||
from sharkiqpy import SharkIqAuthError
|
||||
import pytest
|
||||
from sharkiqpy import AylaApi, SharkIqAuthError
|
||||
|
||||
from homeassistant import config_entries, setup
|
||||
from homeassistant.components.sharkiq.const import DOMAIN
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
from .const import CONFIG, TEST_PASSWORD, TEST_USERNAME, UNIQUE_ID
|
||||
|
||||
from tests.async_mock import MagicMock, PropertyMock, patch
|
||||
from tests.async_mock import patch
|
||||
from tests.common import MockConfigEntry
|
||||
|
||||
|
||||
def _create_mocked_ayla(connect=None):
|
||||
"""Create a mocked AylaApi object."""
|
||||
mocked_ayla = MagicMock()
|
||||
type(mocked_ayla).sign_in = PropertyMock(side_effect=connect)
|
||||
type(mocked_ayla).async_sign_in = PropertyMock(side_effect=connect)
|
||||
return mocked_ayla
|
||||
|
||||
|
||||
async def test_form(hass):
|
||||
"""Test we get the form."""
|
||||
await setup.async_setup_component(hass, "persistent_notification", {})
|
||||
|
@ -46,75 +40,37 @@ async def test_form(hass):
|
|||
"password": TEST_PASSWORD,
|
||||
}
|
||||
await hass.async_block_till_done()
|
||||
assert len(mock_setup.mock_calls) == 1
|
||||
assert len(mock_setup_entry.mock_calls) == 1
|
||||
mock_setup.assert_called_once()
|
||||
mock_setup_entry.assert_called_once()
|
||||
|
||||
|
||||
async def test_form_invalid_auth(hass):
|
||||
"""Test we handle invalid auth."""
|
||||
@pytest.mark.parametrize(
|
||||
"exc,base_error",
|
||||
[
|
||||
(SharkIqAuthError, "invalid_auth"),
|
||||
(aiohttp.ClientError, "cannot_connect"),
|
||||
(TypeError, "unknown"),
|
||||
],
|
||||
)
|
||||
async def test_form_error(hass: HomeAssistant, exc: Exception, base_error: str):
|
||||
"""Test form errors."""
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||
)
|
||||
mocked_ayla = _create_mocked_ayla(connect=SharkIqAuthError)
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.sharkiq.config_flow.get_ayla_api",
|
||||
return_value=mocked_ayla,
|
||||
):
|
||||
with patch.object(AylaApi, "async_sign_in", side_effect=exc):
|
||||
result2 = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
CONFIG,
|
||||
)
|
||||
|
||||
assert result2["type"] == "form"
|
||||
assert result2["errors"] == {"base": "invalid_auth"}
|
||||
assert result2["errors"].get("base") == base_error
|
||||
|
||||
|
||||
async def test_form_cannot_connect(hass):
|
||||
"""Test we handle cannot connect error."""
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||
)
|
||||
mocked_ayla = _create_mocked_ayla(connect=aiohttp.ClientError)
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.sharkiq.config_flow.get_ayla_api",
|
||||
return_value=mocked_ayla,
|
||||
):
|
||||
result2 = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
CONFIG,
|
||||
)
|
||||
|
||||
assert result2["type"] == "form"
|
||||
assert result2["errors"] == {"base": "cannot_connect"}
|
||||
|
||||
|
||||
async def test_form_other_error(hass):
|
||||
"""Test we handle other errors."""
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||
)
|
||||
mocked_ayla = _create_mocked_ayla(connect=TypeError)
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.sharkiq.config_flow.get_ayla_api",
|
||||
return_value=mocked_ayla,
|
||||
):
|
||||
result2 = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"], CONFIG
|
||||
)
|
||||
|
||||
assert result2["type"] == "form"
|
||||
assert result2["errors"] == {"base": "unknown"}
|
||||
|
||||
|
||||
async def test_reauth(hass):
|
||||
async def test_reauth_success(hass: HomeAssistant):
|
||||
"""Test reauth flow."""
|
||||
with patch(
|
||||
"homeassistant.components.sharkiq.vacuum.async_setup_entry",
|
||||
return_value=True,
|
||||
), patch("sharkiqpy.AylaApi.async_sign_in", return_value=True):
|
||||
with patch("sharkiqpy.AylaApi.async_sign_in", return_value=True):
|
||||
mock_config = MockConfigEntry(domain=DOMAIN, unique_id=UNIQUE_ID, data=CONFIG)
|
||||
mock_config.add_to_hass(hass)
|
||||
|
||||
|
@ -125,21 +81,33 @@ async def test_reauth(hass):
|
|||
assert result["type"] == "abort"
|
||||
assert result["reason"] == "reauth_successful"
|
||||
|
||||
with patch("sharkiqpy.AylaApi.async_sign_in", side_effect=SharkIqAuthError):
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN,
|
||||
context={"source": "reauth", "unique_id": UNIQUE_ID},
|
||||
data=CONFIG,
|
||||
)
|
||||
assert result["type"] == "form"
|
||||
assert result["errors"] == {"base": "invalid_auth"}
|
||||
|
||||
with patch("sharkiqpy.AylaApi.async_sign_in", side_effect=RuntimeError):
|
||||
@pytest.mark.parametrize(
|
||||
"side_effect,result_type,msg_field,msg",
|
||||
[
|
||||
(SharkIqAuthError, "form", "errors", "invalid_auth"),
|
||||
(aiohttp.ClientError, "abort", "reason", "cannot_connect"),
|
||||
(TypeError, "abort", "reason", "unknown"),
|
||||
],
|
||||
)
|
||||
async def test_reauth(
|
||||
hass: HomeAssistant,
|
||||
side_effect: Exception,
|
||||
result_type: str,
|
||||
msg_field: str,
|
||||
msg: str,
|
||||
):
|
||||
"""Test reauth failures."""
|
||||
with patch("sharkiqpy.AylaApi.async_sign_in", side_effect=side_effect):
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN,
|
||||
context={"source": "reauth", "unique_id": UNIQUE_ID},
|
||||
data=CONFIG,
|
||||
)
|
||||
|
||||
assert result["type"] == "abort"
|
||||
assert result["reason"] == "unknown"
|
||||
msg_value = result[msg_field]
|
||||
if msg_field == "errors":
|
||||
msg_value = msg_value.get("base")
|
||||
|
||||
assert result["type"] == result_type
|
||||
assert msg_value == msg
|
||||
|
|
|
@ -1,267 +0,0 @@
|
|||
"""Test the Shark IQ vacuum entity."""
|
||||
from copy import deepcopy
|
||||
import enum
|
||||
import json
|
||||
from typing import Dict, List
|
||||
|
||||
from sharkiqpy import AylaApi, Properties, SharkIqAuthError, SharkIqVacuum, get_ayla_api
|
||||
|
||||
from homeassistant.components.sharkiq import SharkIqUpdateCoordinator
|
||||
from homeassistant.components.sharkiq.vacuum import (
|
||||
ATTR_ERROR_CODE,
|
||||
ATTR_ERROR_MSG,
|
||||
ATTR_LOW_LIGHT,
|
||||
ATTR_RECHARGE_RESUME,
|
||||
SharkVacuumEntity,
|
||||
)
|
||||
from homeassistant.components.vacuum import (
|
||||
STATE_CLEANING,
|
||||
STATE_DOCKED,
|
||||
STATE_IDLE,
|
||||
STATE_PAUSED,
|
||||
STATE_RETURNING,
|
||||
SUPPORT_BATTERY,
|
||||
SUPPORT_FAN_SPEED,
|
||||
SUPPORT_LOCATE,
|
||||
SUPPORT_PAUSE,
|
||||
SUPPORT_RETURN_HOME,
|
||||
SUPPORT_START,
|
||||
SUPPORT_STATE,
|
||||
SUPPORT_STATUS,
|
||||
SUPPORT_STOP,
|
||||
)
|
||||
from homeassistant.config_entries import ConfigEntriesFlowManager, ConfigEntry
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.update_coordinator import UpdateFailed
|
||||
|
||||
from .const import (
|
||||
SHARK_DEVICE_DICT,
|
||||
SHARK_METADATA_DICT,
|
||||
SHARK_PROPERTIES_DICT,
|
||||
TEST_PASSWORD,
|
||||
TEST_USERNAME,
|
||||
)
|
||||
|
||||
from tests.async_mock import MagicMock, patch
|
||||
|
||||
MockAyla = MagicMock(spec=AylaApi) # pylint: disable=invalid-name
|
||||
|
||||
|
||||
def _set_property(self, property_name, value):
|
||||
"""Set a property locally without hitting the API."""
|
||||
if isinstance(property_name, enum.Enum):
|
||||
property_name = property_name.value
|
||||
if isinstance(value, enum.Enum):
|
||||
value = value.value
|
||||
self.properties_full[property_name]["value"] = value
|
||||
|
||||
|
||||
async def _async_set_property(self, property_name, value):
|
||||
"""Set a property locally without hitting the API."""
|
||||
_set_property(self, property_name, value)
|
||||
|
||||
|
||||
def _get_mock_shark_vac(ayla_api: AylaApi) -> SharkIqVacuum:
|
||||
"""Create a crude sharkiq vacuum with mocked properties."""
|
||||
shark = SharkIqVacuum(ayla_api, SHARK_DEVICE_DICT)
|
||||
shark.properties_full = deepcopy(SHARK_PROPERTIES_DICT)
|
||||
return shark
|
||||
|
||||
|
||||
async def _async_list_devices(_) -> List[Dict]:
|
||||
"""Generate a dummy of async_list_devices output."""
|
||||
return [SHARK_DEVICE_DICT]
|
||||
|
||||
|
||||
@patch.object(SharkIqVacuum, "set_property_value", new=_set_property)
|
||||
@patch.object(SharkIqVacuum, "async_set_property_value", new=_async_set_property)
|
||||
async def test_shark_operation_modes(hass: HomeAssistant) -> None:
|
||||
"""Test all of the shark vacuum operation modes."""
|
||||
ayla_api = MockAyla()
|
||||
shark_vac = _get_mock_shark_vac(ayla_api)
|
||||
coordinator = SharkIqUpdateCoordinator(hass, None, ayla_api, [shark_vac])
|
||||
shark = SharkVacuumEntity(shark_vac, coordinator)
|
||||
|
||||
# These come from the setup
|
||||
assert isinstance(shark.is_docked, bool) and not shark.is_docked
|
||||
assert (
|
||||
isinstance(shark.recharging_to_resume, bool) and not shark.recharging_to_resume
|
||||
)
|
||||
# Go through the operation modes while it's "off the dock"
|
||||
await shark.async_start()
|
||||
assert shark.operating_mode == shark.state == STATE_CLEANING
|
||||
await shark.async_pause()
|
||||
assert shark.operating_mode == shark.state == STATE_PAUSED
|
||||
await shark.async_stop()
|
||||
assert shark.operating_mode == shark.state == STATE_IDLE
|
||||
await shark.async_return_to_base()
|
||||
assert shark.operating_mode == shark.state == STATE_RETURNING
|
||||
|
||||
# Test the docked modes
|
||||
await shark.async_stop()
|
||||
shark.sharkiq.set_property_value(Properties.RECHARGING_TO_RESUME, 1)
|
||||
shark.sharkiq.set_property_value(Properties.DOCKED_STATUS, 1)
|
||||
assert isinstance(shark.is_docked, bool) and shark.is_docked
|
||||
assert isinstance(shark.recharging_to_resume, bool) and shark.recharging_to_resume
|
||||
assert shark.state == STATE_DOCKED
|
||||
|
||||
shark.sharkiq.set_property_value(Properties.RECHARGING_TO_RESUME, 0)
|
||||
assert shark.state == STATE_DOCKED
|
||||
|
||||
await shark.async_set_fan_speed("Eco")
|
||||
assert shark.fan_speed == "Eco"
|
||||
await shark.async_set_fan_speed("Max")
|
||||
assert shark.fan_speed == "Max"
|
||||
await shark.async_set_fan_speed("Normal")
|
||||
assert shark.fan_speed == "Normal"
|
||||
|
||||
assert set(shark.fan_speed_list) == {"Normal", "Max", "Eco"}
|
||||
|
||||
|
||||
@patch.object(SharkIqVacuum, "set_property_value", new=_set_property)
|
||||
async def test_shark_vac_properties(hass: HomeAssistant) -> None:
|
||||
"""Test all of the shark vacuum property accessors."""
|
||||
ayla_api = MockAyla()
|
||||
shark_vac = _get_mock_shark_vac(ayla_api)
|
||||
coordinator = SharkIqUpdateCoordinator(hass, None, ayla_api, [shark_vac])
|
||||
shark = SharkVacuumEntity(shark_vac, coordinator)
|
||||
|
||||
assert shark.name == "Sharknado"
|
||||
assert shark.serial_number == "AC000Wxxxxxxxxx"
|
||||
assert shark.model == "RV1000A"
|
||||
|
||||
assert shark.battery_level == 50
|
||||
assert shark.fan_speed == "Eco"
|
||||
shark.sharkiq.set_property_value(Properties.POWER_MODE, 0)
|
||||
assert shark.fan_speed == "Normal"
|
||||
assert isinstance(shark.recharge_resume, bool) and shark.recharge_resume
|
||||
assert isinstance(shark.low_light, bool) and not shark.low_light
|
||||
|
||||
target_state_attributes = {
|
||||
ATTR_ERROR_CODE: 7,
|
||||
ATTR_ERROR_MSG: "Cliff sensor is blocked",
|
||||
ATTR_RECHARGE_RESUME: True,
|
||||
ATTR_LOW_LIGHT: False,
|
||||
}
|
||||
state_json = json.dumps(shark.device_state_attributes, sort_keys=True)
|
||||
target_json = json.dumps(target_state_attributes, sort_keys=True)
|
||||
assert state_json == target_json
|
||||
|
||||
assert not shark.should_poll
|
||||
|
||||
|
||||
@patch.object(SharkIqVacuum, "set_property_value", new=_set_property)
|
||||
@patch.object(SharkIqVacuum, "async_set_property_value", new=_async_set_property)
|
||||
async def test_shark_metadata(hass: HomeAssistant) -> None:
|
||||
"""Test shark properties coming from metadata."""
|
||||
ayla_api = MockAyla()
|
||||
shark_vac = _get_mock_shark_vac(ayla_api)
|
||||
coordinator = SharkIqUpdateCoordinator(hass, None, ayla_api, [shark_vac])
|
||||
shark = SharkVacuumEntity(shark_vac, coordinator)
|
||||
shark.sharkiq._update_metadata( # pylint: disable=protected-access
|
||||
SHARK_METADATA_DICT
|
||||
)
|
||||
|
||||
target_device_info = {
|
||||
"identifiers": {("sharkiq", "AC000Wxxxxxxxxx")},
|
||||
"name": "Sharknado",
|
||||
"manufacturer": "Shark",
|
||||
"model": "RV1001AE",
|
||||
"sw_version": "Dummy Firmware 1.0",
|
||||
}
|
||||
|
||||
assert shark.device_info == target_device_info
|
||||
|
||||
|
||||
def _get_async_update(err=None):
|
||||
async def _async_update(_) -> bool:
|
||||
if err is not None:
|
||||
raise err
|
||||
return True
|
||||
|
||||
return _async_update
|
||||
|
||||
|
||||
@patch.object(AylaApi, "async_list_devices", new=_async_list_devices)
|
||||
async def test_updates(hass: HomeAssistant) -> None:
|
||||
"""Test the update coordinator update functions."""
|
||||
ayla_api = get_ayla_api(TEST_USERNAME, TEST_PASSWORD)
|
||||
shark_vac = _get_mock_shark_vac(ayla_api)
|
||||
mock_config = MagicMock(spec=ConfigEntry)
|
||||
coordinator = SharkIqUpdateCoordinator(hass, mock_config, ayla_api, [shark_vac])
|
||||
|
||||
with patch.object(SharkIqVacuum, "async_update", new=_get_async_update()):
|
||||
update_called = (
|
||||
await coordinator._async_update_data() # pylint: disable=protected-access
|
||||
)
|
||||
assert update_called
|
||||
|
||||
update_failed = False
|
||||
with patch.object(
|
||||
SharkIqVacuum, "async_update", new=_get_async_update(SharkIqAuthError)
|
||||
), patch.object(HomeAssistant, "async_create_task"), patch.object(
|
||||
ConfigEntriesFlowManager, "async_init"
|
||||
):
|
||||
try:
|
||||
await coordinator._async_update_data() # pylint: disable=protected-access
|
||||
except UpdateFailed:
|
||||
update_failed = True
|
||||
assert update_failed
|
||||
|
||||
|
||||
async def test_coordinator_match(hass: HomeAssistant):
|
||||
"""Test that sharkiq-coordinator references work."""
|
||||
ayla_api = get_ayla_api(TEST_PASSWORD, TEST_USERNAME)
|
||||
shark_vac1 = _get_mock_shark_vac(ayla_api)
|
||||
shark_vac2 = _get_mock_shark_vac(ayla_api)
|
||||
shark_vac2._dsn = "FOOBAR!" # pylint: disable=protected-access
|
||||
|
||||
coordinator = SharkIqUpdateCoordinator(hass, None, ayla_api, [shark_vac1])
|
||||
|
||||
api = SharkVacuumEntity(shark_vac1, coordinator)
|
||||
coordinator.last_update_success = True
|
||||
coordinator._online_dsns = set() # pylint: disable=protected-access
|
||||
assert not api.is_online
|
||||
assert not api.available
|
||||
|
||||
coordinator._online_dsns = { # pylint: disable=protected-access
|
||||
shark_vac1.serial_number
|
||||
}
|
||||
assert api.is_online
|
||||
assert api.available
|
||||
|
||||
coordinator.last_update_success = False
|
||||
assert not api.available
|
||||
|
||||
|
||||
async def test_simple_properties(hass: HomeAssistant):
|
||||
"""Test that simple properties work as intended."""
|
||||
ayla_api = get_ayla_api(TEST_PASSWORD, TEST_USERNAME)
|
||||
shark_vac1 = _get_mock_shark_vac(ayla_api)
|
||||
coordinator = SharkIqUpdateCoordinator(hass, None, ayla_api, [shark_vac1])
|
||||
entity = SharkVacuumEntity(shark_vac1, coordinator)
|
||||
|
||||
assert entity.unique_id == "AC000Wxxxxxxxxx"
|
||||
|
||||
assert entity.supported_features == (
|
||||
SUPPORT_BATTERY
|
||||
| SUPPORT_FAN_SPEED
|
||||
| SUPPORT_PAUSE
|
||||
| SUPPORT_RETURN_HOME
|
||||
| SUPPORT_START
|
||||
| SUPPORT_STATE
|
||||
| SUPPORT_STATUS
|
||||
| SUPPORT_STOP
|
||||
| SUPPORT_LOCATE
|
||||
)
|
||||
|
||||
assert entity.error_code == 7
|
||||
assert entity.error_message == "Cliff sensor is blocked"
|
||||
shark_vac1.properties_full[Properties.ERROR_CODE.value]["value"] = 0
|
||||
assert entity.error_code == 0
|
||||
assert entity.error_message is None
|
||||
|
||||
assert (
|
||||
coordinator.online_dsns
|
||||
is coordinator._online_dsns # pylint: disable=protected-access
|
||||
)
|
242
tests/components/sharkiq/test_vacuum.py
Normal file
242
tests/components/sharkiq/test_vacuum.py
Normal file
|
@ -0,0 +1,242 @@
|
|||
"""Test the Shark IQ vacuum entity."""
|
||||
from copy import deepcopy
|
||||
import enum
|
||||
from typing import Any, Iterable, List, Optional
|
||||
|
||||
import pytest
|
||||
from sharkiqpy import AylaApi, SharkIqAuthError, SharkIqVacuum
|
||||
|
||||
from homeassistant.components.homeassistant import SERVICE_UPDATE_ENTITY
|
||||
from homeassistant.components.sharkiq import DOMAIN
|
||||
from homeassistant.components.sharkiq.vacuum import (
|
||||
ATTR_ERROR_CODE,
|
||||
ATTR_ERROR_MSG,
|
||||
ATTR_LOW_LIGHT,
|
||||
ATTR_RECHARGE_RESUME,
|
||||
FAN_SPEEDS_MAP,
|
||||
)
|
||||
from homeassistant.components.vacuum import (
|
||||
ATTR_BATTERY_LEVEL,
|
||||
ATTR_FAN_SPEED,
|
||||
ATTR_FAN_SPEED_LIST,
|
||||
SERVICE_LOCATE,
|
||||
SERVICE_PAUSE,
|
||||
SERVICE_RETURN_TO_BASE,
|
||||
SERVICE_SET_FAN_SPEED,
|
||||
SERVICE_START,
|
||||
SERVICE_STOP,
|
||||
STATE_CLEANING,
|
||||
STATE_IDLE,
|
||||
STATE_PAUSED,
|
||||
STATE_RETURNING,
|
||||
SUPPORT_BATTERY,
|
||||
SUPPORT_FAN_SPEED,
|
||||
SUPPORT_LOCATE,
|
||||
SUPPORT_PAUSE,
|
||||
SUPPORT_RETURN_HOME,
|
||||
SUPPORT_START,
|
||||
SUPPORT_STATE,
|
||||
SUPPORT_STATUS,
|
||||
SUPPORT_STOP,
|
||||
)
|
||||
from homeassistant.const import (
|
||||
ATTR_ENTITY_ID,
|
||||
ATTR_SUPPORTED_FEATURES,
|
||||
STATE_UNAVAILABLE,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.setup import async_setup_component
|
||||
|
||||
from .const import (
|
||||
CONFIG,
|
||||
ENTRY_ID,
|
||||
SHARK_DEVICE_DICT,
|
||||
SHARK_METADATA_DICT,
|
||||
SHARK_PROPERTIES_DICT,
|
||||
TEST_USERNAME,
|
||||
)
|
||||
|
||||
from tests.async_mock import patch
|
||||
from tests.common import MockConfigEntry
|
||||
|
||||
VAC_ENTITY_ID = f"vacuum.{SHARK_DEVICE_DICT['product_name'].lower()}"
|
||||
EXPECTED_FEATURES = (
|
||||
SUPPORT_BATTERY
|
||||
| SUPPORT_FAN_SPEED
|
||||
| SUPPORT_PAUSE
|
||||
| SUPPORT_RETURN_HOME
|
||||
| SUPPORT_START
|
||||
| SUPPORT_STATE
|
||||
| SUPPORT_STATUS
|
||||
| SUPPORT_STOP
|
||||
| SUPPORT_LOCATE
|
||||
)
|
||||
|
||||
|
||||
class MockAyla(AylaApi):
|
||||
"""Mocked AylaApi that doesn't do anything."""
|
||||
|
||||
async def async_sign_in(self):
|
||||
"""Instead of signing in, just return."""
|
||||
|
||||
async def async_list_devices(self) -> List[dict]:
|
||||
"""Return the device list."""
|
||||
return [SHARK_DEVICE_DICT]
|
||||
|
||||
async def async_get_devices(self, update: bool = True) -> List[SharkIqVacuum]:
|
||||
"""Get the list of devices."""
|
||||
shark = MockShark(self, SHARK_DEVICE_DICT)
|
||||
shark.properties_full = deepcopy(SHARK_PROPERTIES_DICT)
|
||||
shark._update_metadata(SHARK_METADATA_DICT) # pylint: disable=protected-access
|
||||
return [shark]
|
||||
|
||||
async def async_request(self, http_method: str, url: str, **kwargs):
|
||||
"""Don't make an HTTP request."""
|
||||
|
||||
|
||||
class MockShark(SharkIqVacuum):
|
||||
"""Mocked SharkIqVacuum that won't hit the API."""
|
||||
|
||||
async def async_update(self, property_list: Optional[Iterable[str]] = None):
|
||||
"""Don't do anything."""
|
||||
|
||||
def set_property_value(self, property_name, value):
|
||||
"""Set a property locally without hitting the API."""
|
||||
if isinstance(property_name, enum.Enum):
|
||||
property_name = property_name.value
|
||||
if isinstance(value, enum.Enum):
|
||||
value = value.value
|
||||
self.properties_full[property_name]["value"] = value
|
||||
|
||||
async def async_set_property_value(self, property_name, value):
|
||||
"""Set a property locally without hitting the API."""
|
||||
self.set_property_value(property_name, value)
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
@patch("sharkiqpy.ayla_api.AylaApi", MockAyla)
|
||||
async def setup_integration(hass):
|
||||
"""Build the mock integration."""
|
||||
entry = MockConfigEntry(
|
||||
domain=DOMAIN, unique_id=TEST_USERNAME, data=CONFIG, entry_id=ENTRY_ID
|
||||
)
|
||||
entry.add_to_hass(hass)
|
||||
await hass.config_entries.async_setup(entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
|
||||
async def test_simple_properties(hass: HomeAssistant):
|
||||
"""Test that simple properties work as intended."""
|
||||
state = hass.states.get(VAC_ENTITY_ID)
|
||||
registry = await hass.helpers.entity_registry.async_get_registry()
|
||||
entity = registry.async_get(VAC_ENTITY_ID)
|
||||
|
||||
assert entity
|
||||
assert state
|
||||
assert state.state == STATE_CLEANING
|
||||
assert entity.unique_id == "AC000Wxxxxxxxxx"
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"attribute,target_value",
|
||||
[
|
||||
(ATTR_SUPPORTED_FEATURES, EXPECTED_FEATURES),
|
||||
(ATTR_BATTERY_LEVEL, 50),
|
||||
(ATTR_FAN_SPEED, "Eco"),
|
||||
(ATTR_FAN_SPEED_LIST, list(FAN_SPEEDS_MAP)),
|
||||
(ATTR_ERROR_CODE, 7),
|
||||
(ATTR_ERROR_MSG, "Cliff sensor is blocked"),
|
||||
(ATTR_LOW_LIGHT, False),
|
||||
(ATTR_RECHARGE_RESUME, True),
|
||||
],
|
||||
)
|
||||
async def test_initial_attributes(
|
||||
hass: HomeAssistant, attribute: str, target_value: Any
|
||||
):
|
||||
"""Test initial config attributes."""
|
||||
state = hass.states.get(VAC_ENTITY_ID)
|
||||
assert state.attributes.get(attribute) == target_value
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"service,target_state",
|
||||
[
|
||||
(SERVICE_STOP, STATE_IDLE),
|
||||
(SERVICE_PAUSE, STATE_PAUSED),
|
||||
(SERVICE_RETURN_TO_BASE, STATE_RETURNING),
|
||||
(SERVICE_START, STATE_CLEANING),
|
||||
],
|
||||
)
|
||||
async def test_cleaning_states(hass: HomeAssistant, service: str, target_state: str):
|
||||
"""Test cleaning states."""
|
||||
service_data = {ATTR_ENTITY_ID: VAC_ENTITY_ID}
|
||||
await hass.services.async_call("vacuum", service, service_data, blocking=True)
|
||||
state = hass.states.get(VAC_ENTITY_ID)
|
||||
assert state.state == target_state
|
||||
|
||||
|
||||
@pytest.mark.parametrize("fan_speed", list(FAN_SPEEDS_MAP))
|
||||
async def test_fan_speed(hass: HomeAssistant, fan_speed: str) -> None:
|
||||
"""Test setting fan speeds."""
|
||||
service_data = {ATTR_ENTITY_ID: VAC_ENTITY_ID, ATTR_FAN_SPEED: fan_speed}
|
||||
await hass.services.async_call(
|
||||
"vacuum", SERVICE_SET_FAN_SPEED, service_data, blocking=True
|
||||
)
|
||||
state = hass.states.get(VAC_ENTITY_ID)
|
||||
assert state.attributes.get(ATTR_FAN_SPEED) == fan_speed
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"device_property,target_value",
|
||||
[
|
||||
("manufacturer", "Shark"),
|
||||
("model", "RV1001AE"),
|
||||
("name", "Sharknado"),
|
||||
("sw_version", "Dummy Firmware 1.0"),
|
||||
],
|
||||
)
|
||||
async def test_device_properties(
|
||||
hass: HomeAssistant, device_property: str, target_value: str
|
||||
):
|
||||
"""Test device properties."""
|
||||
registry = await hass.helpers.device_registry.async_get_registry()
|
||||
device = registry.async_get_device({(DOMAIN, "AC000Wxxxxxxxxx")}, [])
|
||||
assert getattr(device, device_property) == target_value
|
||||
|
||||
|
||||
async def test_locate(hass):
|
||||
"""Test that the locate command works."""
|
||||
with patch.object(SharkIqVacuum, "async_find_device") as mock_locate:
|
||||
data = {ATTR_ENTITY_ID: VAC_ENTITY_ID}
|
||||
await hass.services.async_call("vacuum", SERVICE_LOCATE, data, blocking=True)
|
||||
mock_locate.assert_called_once()
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"side_effect,success",
|
||||
[
|
||||
(None, True),
|
||||
(SharkIqAuthError, False),
|
||||
(RuntimeError, False),
|
||||
],
|
||||
)
|
||||
async def test_coordinator_updates(
|
||||
hass: HomeAssistant, side_effect: Optional[Exception], success: bool
|
||||
) -> None:
|
||||
"""Test the update coordinator update functions."""
|
||||
coordinator = hass.data[DOMAIN][ENTRY_ID]
|
||||
|
||||
await async_setup_component(hass, "homeassistant", {})
|
||||
|
||||
with patch.object(
|
||||
MockShark, "async_update", side_effect=side_effect
|
||||
) as mock_update:
|
||||
data = {ATTR_ENTITY_ID: [VAC_ENTITY_ID]}
|
||||
await hass.services.async_call(
|
||||
"homeassistant", SERVICE_UPDATE_ENTITY, data, blocking=True
|
||||
)
|
||||
assert coordinator.last_update_success == success
|
||||
mock_update.assert_called_once()
|
||||
|
||||
state = hass.states.get(VAC_ENTITY_ID)
|
||||
assert (state.state == STATE_UNAVAILABLE) != success
|
Loading…
Add table
Reference in a new issue