diff --git a/tests/components/litterrobot/common.py b/tests/components/litterrobot/common.py index 19a6b5617c7..f5b4e32a1e1 100644 --- a/tests/components/litterrobot/common.py +++ b/tests/components/litterrobot/common.py @@ -1,6 +1,4 @@ """Common utils for Litter-Robot tests.""" -from datetime import datetime - from homeassistant.components.litterrobot import DOMAIN from homeassistant.const import CONF_PASSWORD, CONF_USERNAME @@ -11,7 +9,7 @@ ROBOT_NAME = "Test" ROBOT_SERIAL = "LR3C012345" ROBOT_DATA = { "powerStatus": "AC", - "lastSeen": datetime.now().isoformat(), + "lastSeen": "2022-09-17T13:06:37.884Z", "cleanCycleWaitTimeMinutes": "7", "unitStatus": "RDY", "litterRobotNickname": ROBOT_NAME, @@ -24,5 +22,123 @@ ROBOT_DATA = { "nightLightActive": "1", "sleepModeActive": "112:50:19", } +ROBOT_4_DATA = { + "name": ROBOT_NAME, + "serial": "LR4C010001", + "userId": "1234567", + "espFirmware": "1.1.50", + "picFirmwareVersion": "10512.2560.2.53", + "laserBoardFirmwareVersion": "4.0.65.4", + "wifiRssi": -53.0, + "unitPowerType": "AC", + "catWeight": 12.0, + "unitTimezone": "America/New_York", + "unitTime": None, + "cleanCycleWaitTime": 15, + "isKeypadLockout": False, + "nightLightMode": "OFF", + "nightLightBrightness": 85, + "isPanelSleepMode": False, + "panelSleepTime": 0, + "panelWakeTime": 0, + "weekdaySleepModeEnabled": { + "Sunday": {"sleepTime": 0, "wakeTime": 0, "isEnabled": False}, + "Monday": {"sleepTime": 0, "wakeTime": 180, "isEnabled": True}, + "Tuesday": {"sleepTime": 0, "wakeTime": 180, "isEnabled": True}, + "Wednesday": {"sleepTime": 0, "wakeTime": 180, "isEnabled": True}, + "Thursday": {"sleepTime": 0, "wakeTime": 180, "isEnabled": True}, + "Friday": {"sleepTime": 0, "wakeTime": 180, "isEnabled": True}, + "Saturday": {"sleepTime": 0, "wakeTime": 0, "isEnabled": False}, + }, + "unitPowerStatus": "ON", + "sleepStatus": "WAKE", + "robotStatus": "ROBOT_IDLE", + "globeMotorFaultStatus": "FAULT_CLEAR", + "pinchStatus": "CLEAR", + "catDetect": "CAT_DETECT_CLEAR", + "isBonnetRemoved": False, + "isNightLightLEDOn": False, + "odometerPowerCycles": 8, + "odometerCleanCycles": 158, + "odometerEmptyCycles": 1, + "odometerFilterCycles": 0, + "isDFIResetPending": False, + "DFINumberOfCycles": 104, + "DFILevelPercent": 76, + "isDFIFull": True, + "DFIFullCounter": 3, + "DFITriggerCount": 42, + "litterLevel": 460, + "DFILevelMM": 115, + "isCatDetectPending": False, + "globeMotorRetractFaultStatus": "FAULT_CLEAR", + "robotCycleStatus": "CYCLE_IDLE", + "robotCycleState": "CYCLE_STATE_WAIT_ON", + "weightSensor": -3.0, + "isOnline": True, + "isOnboarded": True, + "isProvisioned": True, + "isDebugModeActive": False, + "lastSeen": "2022-09-17T12:06:37.884Z", + "sessionId": "abcdef12-e358-4b6c-9022-012345678912", + "setupDateTime": "2022-08-28T17:01:12.644Z", + "isFirmwareUpdateTriggered": False, + "firmwareUpdateStatus": "NONE", + "wifiModeStatus": "ROUTER_CONNECTED", + "isUSBPowerOn": True, + "USBFaultStatus": "CLEAR", + "isDFIPartialFull": True, +} +FEEDER_ROBOT_DATA = { + "id": 1, + "name": ROBOT_NAME, + "serial": "RF1C000001", + "timezone": "America/Denver", + "isEighthCupEnabled": False, + "created_at": "2021-12-15T06:45:00.000000+00:00", + "household_id": 1, + "state": { + "id": 1, + "info": { + "level": 2, + "power": True, + "online": True, + "acPower": True, + "dcPower": False, + "gravity": False, + "chuteFull": False, + "fwVersion": "1.0.0", + "onBoarded": True, + "unitMeals": 0, + "motorJammed": False, + "chuteFullExt": False, + "panelLockout": False, + "unitPortions": 0, + "autoNightMode": True, + "mealInsertSize": 1, + }, + "updated_at": "2022-09-08T15:07:00.000000+00:00", + }, + "feeding_snack": [ + {"timestamp": "2022-09-04T03:03:00.000000+00:00", "amount": 0.125}, + {"timestamp": "2022-08-30T16:34:00.000000+00:00", "amount": 0.25}, + ], + "feeding_meal": [ + { + "timestamp": "2022-09-08T18:00:00.000000+00:00", + "amount": 0.125, + "meal_name": "Lunch", + "meal_number": 2, + "meal_total_portions": 2, + }, + { + "timestamp": "2022-09-08T12:00:00.000000+00:00", + "amount": 0.125, + "meal_name": "Breakfast", + "meal_number": 1, + "meal_total_portions": 1, + }, + ], +} VACUUM_ENTITY_ID = "vacuum.test_litter_box" diff --git a/tests/components/litterrobot/conftest.py b/tests/components/litterrobot/conftest.py index 34132ec66d6..ce80471797d 100644 --- a/tests/components/litterrobot/conftest.py +++ b/tests/components/litterrobot/conftest.py @@ -4,26 +4,35 @@ from __future__ import annotations from typing import Any from unittest.mock import AsyncMock, MagicMock, patch -from pylitterbot import Account, LitterRobot3, Robot +from pylitterbot import Account, FeederRobot, LitterRobot3, LitterRobot4, Robot from pylitterbot.exceptions import InvalidCommandException import pytest from homeassistant.components import litterrobot from homeassistant.core import HomeAssistant -from .common import CONFIG, ROBOT_DATA +from .common import CONFIG, FEEDER_ROBOT_DATA, ROBOT_4_DATA, ROBOT_DATA from tests.common import MockConfigEntry def create_mock_robot( - robot_data: dict | None, account: Account, side_effect: Any | None = None + robot_data: dict | None, + account: Account, + v4: bool, + feeder: bool, + side_effect: Any | None = None, ) -> Robot: """Create a mock Litter-Robot device.""" if not robot_data: robot_data = {} - robot = LitterRobot3(data={**ROBOT_DATA, **robot_data}, account=account) + if v4: + robot = LitterRobot4(data={**ROBOT_4_DATA, **robot_data}, account=account) + elif feeder: + robot = FeederRobot(data={**FEEDER_ROBOT_DATA, **robot_data}, account=account) + else: + robot = LitterRobot3(data={**ROBOT_DATA, **robot_data}, account=account) robot.start_cleaning = AsyncMock(side_effect=side_effect) robot.set_power_status = AsyncMock(side_effect=side_effect) robot.reset_waste_drawer = AsyncMock(side_effect=side_effect) @@ -39,13 +48,17 @@ def create_mock_account( robot_data: dict | None = None, side_effect: Any | None = None, skip_robots: bool = False, + v4: bool = False, + feeder: bool = False, ) -> MagicMock: """Create a mock Litter-Robot account.""" account = MagicMock(spec=Account) account.connect = AsyncMock() account.refresh_robots = AsyncMock() account.robots = ( - [] if skip_robots else [create_mock_robot(robot_data, account, side_effect)] + [] + if skip_robots + else [create_mock_robot(robot_data, account, v4, feeder, side_effect)] ) return account @@ -56,6 +69,18 @@ def mock_account() -> MagicMock: return create_mock_account() +@pytest.fixture +def mock_account_with_litterrobot_4() -> MagicMock: + """Mock account with Litter-Robot 4.""" + return create_mock_account(v4=True) + + +@pytest.fixture +def mock_account_with_feederrobot() -> MagicMock: + """Mock account with Feeder-Robot.""" + return create_mock_account(feeder=True) + + @pytest.fixture def mock_account_with_no_robots() -> MagicMock: """Mock a Litter-Robot account.""" diff --git a/tests/components/litterrobot/test_sensor.py b/tests/components/litterrobot/test_sensor.py index ce91541f0d7..77668a9e592 100644 --- a/tests/components/litterrobot/test_sensor.py +++ b/tests/components/litterrobot/test_sensor.py @@ -2,12 +2,13 @@ from unittest.mock import MagicMock from homeassistant.components.sensor import DOMAIN as PLATFORM_DOMAIN, SensorDeviceClass -from homeassistant.const import PERCENTAGE, STATE_UNKNOWN +from homeassistant.const import MASS_POUNDS, PERCENTAGE, STATE_UNKNOWN from homeassistant.core import HomeAssistant from .conftest import setup_integration WASTE_DRAWER_ENTITY_ID = "sensor.test_waste_drawer" +SLEEP_END_TIME_ENTITY_ID = "sensor.test_sleep_mode_end_time" SLEEP_START_TIME_ENTITY_ID = "sensor.test_sleep_mode_start_time" @@ -36,6 +37,10 @@ async def test_sleep_time_sensor_with_sleep_disabled( assert sensor.state == STATE_UNKNOWN assert sensor.attributes["device_class"] == SensorDeviceClass.TIMESTAMP + sensor = hass.states.get(SLEEP_END_TIME_ENTITY_ID) + assert sensor.state == STATE_UNKNOWN + assert sensor.attributes["device_class"] == SensorDeviceClass.TIMESTAMP + async def test_gauge_icon() -> None: """Test icon generator for gauge sensor.""" @@ -59,3 +64,39 @@ async def test_gauge_icon() -> None: assert icon_for_gauge_level(40, 10) == GAUGE_LOW assert icon_for_gauge_level(80, 10) == GAUGE assert icon_for_gauge_level(100, 10) == GAUGE_FULL + + +async def test_litter_robot_sensor( + hass: HomeAssistant, mock_account_with_litterrobot_4: MagicMock +) -> None: + """Tests Litter-Robot sensors.""" + await setup_integration(hass, mock_account_with_litterrobot_4, PLATFORM_DOMAIN) + + sensor = hass.states.get(SLEEP_START_TIME_ENTITY_ID) + assert sensor.state == "2022-09-19T04:00:00+00:00" + assert sensor.attributes["device_class"] == SensorDeviceClass.TIMESTAMP + sensor = hass.states.get(SLEEP_END_TIME_ENTITY_ID) + assert sensor.state == "2022-09-16T07:00:00+00:00" + assert sensor.attributes["device_class"] == SensorDeviceClass.TIMESTAMP + sensor = hass.states.get("sensor.test_last_seen") + assert sensor.state == "2022-09-17T12:06:37+00:00" + assert sensor.attributes["device_class"] == SensorDeviceClass.TIMESTAMP + sensor = hass.states.get("sensor.test_status_code") + assert sensor.state == "dfs" + assert sensor.attributes["device_class"] == "litterrobot__status_code" + sensor = hass.states.get("sensor.test_litter_level") + assert sensor.state == "70.0" + assert sensor.attributes["unit_of_measurement"] == PERCENTAGE + sensor = hass.states.get("sensor.test_pet_weight") + assert sensor.state == "12.0" + assert sensor.attributes["unit_of_measurement"] == MASS_POUNDS + + +async def test_feeder_robot_sensor( + hass: HomeAssistant, mock_account_with_feederrobot: MagicMock +) -> None: + """Tests Feeder-Robot sensors.""" + await setup_integration(hass, mock_account_with_feederrobot, PLATFORM_DOMAIN) + sensor = hass.states.get("sensor.test_food_level") + assert sensor.state == "20" + assert sensor.attributes["unit_of_measurement"] == PERCENTAGE