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/sesame/lock.py
|
||||||
homeassistant/components/seven_segments/image_processing.py
|
homeassistant/components/seven_segments/image_processing.py
|
||||||
homeassistant/components/seventeentrack/sensor.py
|
homeassistant/components/seventeentrack/sensor.py
|
||||||
homeassistant/components/sharkiq/vacuum.py
|
|
||||||
homeassistant/components/shiftr/*
|
homeassistant/components/shiftr/*
|
||||||
homeassistant/components/shodan/sensor.py
|
homeassistant/components/shodan/sensor.py
|
||||||
homeassistant/components/shelly/__init__.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)
|
shark_vacs = await ayla_api.async_get_devices(False)
|
||||||
device_names = ", ".join([d.name for d in shark_vacs])
|
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)
|
coordinator = SharkIqUpdateCoordinator(hass, config_entry, ayla_api, shark_vacs)
|
||||||
|
|
||||||
await coordinator.async_refresh()
|
await coordinator.async_refresh()
|
||||||
|
|
|
@ -71,3 +71,4 @@ TEST_USERNAME = "test-username"
|
||||||
TEST_PASSWORD = "test-password"
|
TEST_PASSWORD = "test-password"
|
||||||
UNIQUE_ID = "foo@bar.com"
|
UNIQUE_ID = "foo@bar.com"
|
||||||
CONFIG = {CONF_USERNAME: TEST_USERNAME, CONF_PASSWORD: TEST_PASSWORD}
|
CONFIG = {CONF_USERNAME: TEST_USERNAME, CONF_PASSWORD: TEST_PASSWORD}
|
||||||
|
ENTRY_ID = "0123456789abcdef0123456789abcdef"
|
||||||
|
|
|
@ -1,24 +1,18 @@
|
||||||
"""Test the Shark IQ config flow."""
|
"""Test the Shark IQ config flow."""
|
||||||
import aiohttp
|
import aiohttp
|
||||||
from sharkiqpy import SharkIqAuthError
|
import pytest
|
||||||
|
from sharkiqpy import AylaApi, SharkIqAuthError
|
||||||
|
|
||||||
from homeassistant import config_entries, setup
|
from homeassistant import config_entries, setup
|
||||||
from homeassistant.components.sharkiq.const import DOMAIN
|
from homeassistant.components.sharkiq.const import DOMAIN
|
||||||
|
from homeassistant.core import HomeAssistant
|
||||||
|
|
||||||
from .const import CONFIG, TEST_PASSWORD, TEST_USERNAME, UNIQUE_ID
|
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
|
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):
|
async def test_form(hass):
|
||||||
"""Test we get the form."""
|
"""Test we get the form."""
|
||||||
await setup.async_setup_component(hass, "persistent_notification", {})
|
await setup.async_setup_component(hass, "persistent_notification", {})
|
||||||
|
@ -46,75 +40,37 @@ async def test_form(hass):
|
||||||
"password": TEST_PASSWORD,
|
"password": TEST_PASSWORD,
|
||||||
}
|
}
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
assert len(mock_setup.mock_calls) == 1
|
mock_setup.assert_called_once()
|
||||||
assert len(mock_setup_entry.mock_calls) == 1
|
mock_setup_entry.assert_called_once()
|
||||||
|
|
||||||
|
|
||||||
async def test_form_invalid_auth(hass):
|
@pytest.mark.parametrize(
|
||||||
"""Test we handle invalid auth."""
|
"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(
|
result = await hass.config_entries.flow.async_init(
|
||||||
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||||
)
|
)
|
||||||
mocked_ayla = _create_mocked_ayla(connect=SharkIqAuthError)
|
|
||||||
|
|
||||||
with patch(
|
with patch.object(AylaApi, "async_sign_in", side_effect=exc):
|
||||||
"homeassistant.components.sharkiq.config_flow.get_ayla_api",
|
|
||||||
return_value=mocked_ayla,
|
|
||||||
):
|
|
||||||
result2 = await hass.config_entries.flow.async_configure(
|
result2 = await hass.config_entries.flow.async_configure(
|
||||||
result["flow_id"],
|
result["flow_id"],
|
||||||
CONFIG,
|
CONFIG,
|
||||||
)
|
)
|
||||||
|
|
||||||
assert result2["type"] == "form"
|
assert result2["type"] == "form"
|
||||||
assert result2["errors"] == {"base": "invalid_auth"}
|
assert result2["errors"].get("base") == base_error
|
||||||
|
|
||||||
|
|
||||||
async def test_form_cannot_connect(hass):
|
async def test_reauth_success(hass: HomeAssistant):
|
||||||
"""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):
|
|
||||||
"""Test reauth flow."""
|
"""Test reauth flow."""
|
||||||
with patch(
|
with patch("sharkiqpy.AylaApi.async_sign_in", return_value=True):
|
||||||
"homeassistant.components.sharkiq.vacuum.async_setup_entry",
|
|
||||||
return_value=True,
|
|
||||||
), patch("sharkiqpy.AylaApi.async_sign_in", return_value=True):
|
|
||||||
mock_config = MockConfigEntry(domain=DOMAIN, unique_id=UNIQUE_ID, data=CONFIG)
|
mock_config = MockConfigEntry(domain=DOMAIN, unique_id=UNIQUE_ID, data=CONFIG)
|
||||||
mock_config.add_to_hass(hass)
|
mock_config.add_to_hass(hass)
|
||||||
|
|
||||||
|
@ -125,21 +81,33 @@ async def test_reauth(hass):
|
||||||
assert result["type"] == "abort"
|
assert result["type"] == "abort"
|
||||||
assert result["reason"] == "reauth_successful"
|
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(
|
result = await hass.config_entries.flow.async_init(
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
context={"source": "reauth", "unique_id": UNIQUE_ID},
|
context={"source": "reauth", "unique_id": UNIQUE_ID},
|
||||||
data=CONFIG,
|
data=CONFIG,
|
||||||
)
|
)
|
||||||
|
|
||||||
assert result["type"] == "abort"
|
msg_value = result[msg_field]
|
||||||
assert result["reason"] == "unknown"
|
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