Bump aioautomower to 2024.10.3 (#128788)

This commit is contained in:
Thomas55555 2024-10-24 21:56:38 +02:00 committed by GitHub
parent bd55fe868d
commit 1c5193aa4d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
21 changed files with 203 additions and 255 deletions

View File

@ -14,6 +14,7 @@ from homeassistant.helpers import (
config_entry_oauth2_flow, config_entry_oauth2_flow,
device_registry as dr, device_registry as dr,
) )
from homeassistant.util import dt as dt_util
from . import api from . import api
from .const import DOMAIN from .const import DOMAIN
@ -48,7 +49,11 @@ async def async_setup_entry(hass: HomeAssistant, entry: AutomowerConfigEntry) ->
aiohttp_client.async_get_clientsession(hass), aiohttp_client.async_get_clientsession(hass),
session, session,
) )
automower_api = AutomowerSession(api_api) time_zone_str = str(dt_util.DEFAULT_TIME_ZONE)
automower_api = AutomowerSession(
api_api,
await dt_util.async_get_time_zone(time_zone_str),
)
try: try:
await api_api.async_get_access_token() await api_api.async_get_access_token()
except ClientResponseError as err: except ClientResponseError as err:

View File

@ -11,7 +11,6 @@ from aioautomower.session import AutomowerSession
from homeassistant.components.button import ButtonEntity, ButtonEntityDescription from homeassistant.components.button import ButtonEntity, ButtonEntityDescription
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.util import dt as dt_util
from . import AutomowerConfigEntry from . import AutomowerConfigEntry
from .coordinator import AutomowerDataUpdateCoordinator from .coordinator import AutomowerDataUpdateCoordinator
@ -24,19 +23,6 @@ from .entity import (
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
async def _async_set_time(
session: AutomowerSession,
mower_id: str,
) -> None:
"""Set datetime for the mower."""
# dt_util returns the current (aware) local datetime, set in the frontend.
# We assume it's the timezone in which the mower is.
await session.commands.set_datetime(
mower_id,
dt_util.now(),
)
@dataclass(frozen=True, kw_only=True) @dataclass(frozen=True, kw_only=True)
class AutomowerButtonEntityDescription(ButtonEntityDescription): class AutomowerButtonEntityDescription(ButtonEntityDescription):
"""Describes Automower button entities.""" """Describes Automower button entities."""
@ -58,7 +44,7 @@ MOWER_BUTTON_TYPES: tuple[AutomowerButtonEntityDescription, ...] = (
key="sync_clock", key="sync_clock",
translation_key="sync_clock", translation_key="sync_clock",
available_fn=_check_error_free, available_fn=_check_error_free,
press_fn=_async_set_time, press_fn=lambda session, mower_id: session.commands.set_datetime(mower_id),
), ),
) )

View File

@ -60,8 +60,8 @@ class AutomowerCalendarEntity(AutomowerBaseEntity, CalendarEntity):
] ]
return CalendarEvent( return CalendarEvent(
summary=make_name_string(work_area_name, program_event.schedule_no), summary=make_name_string(work_area_name, program_event.schedule_no),
start=program_event.start.replace(tzinfo=dt_util.DEFAULT_TIME_ZONE), start=program_event.start,
end=program_event.end.replace(tzinfo=dt_util.DEFAULT_TIME_ZONE), end=program_event.end,
rrule=program_event.rrule_str, rrule=program_event.rrule_str,
) )

View File

@ -7,5 +7,5 @@
"documentation": "https://www.home-assistant.io/integrations/husqvarna_automower", "documentation": "https://www.home-assistant.io/integrations/husqvarna_automower",
"iot_class": "cloud_push", "iot_class": "cloud_push",
"loggers": ["aioautomower"], "loggers": ["aioautomower"],
"requirements": ["aioautomower==2024.10.0"] "requirements": ["aioautomower==2024.10.3"]
} }

View File

@ -4,8 +4,8 @@ from collections.abc import Callable, Mapping
from dataclasses import dataclass from dataclasses import dataclass
from datetime import datetime from datetime import datetime
import logging import logging
from operator import attrgetter
from typing import TYPE_CHECKING, Any from typing import TYPE_CHECKING, Any
from zoneinfo import ZoneInfo
from aioautomower.model import ( from aioautomower.model import (
MowerAttributes, MowerAttributes,
@ -14,7 +14,6 @@ from aioautomower.model import (
RestrictedReasons, RestrictedReasons,
WorkArea, WorkArea,
) )
from aioautomower.utils import naive_to_aware
from homeassistant.components.sensor import ( from homeassistant.components.sensor import (
SensorDeviceClass, SensorDeviceClass,
@ -26,7 +25,6 @@ from homeassistant.const import PERCENTAGE, EntityCategory, UnitOfLength, UnitOf
from homeassistant.core import HomeAssistant, callback from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import StateType from homeassistant.helpers.typing import StateType
from homeassistant.util import dt as dt_util
from . import AutomowerConfigEntry from . import AutomowerConfigEntry
from .coordinator import AutomowerDataUpdateCoordinator from .coordinator import AutomowerDataUpdateCoordinator
@ -196,16 +194,16 @@ ERROR_STATES = {
} }
RESTRICTED_REASONS: list = [ RESTRICTED_REASONS: list = [
RestrictedReasons.ALL_WORK_AREAS_COMPLETED.lower(), RestrictedReasons.ALL_WORK_AREAS_COMPLETED,
RestrictedReasons.DAILY_LIMIT.lower(), RestrictedReasons.DAILY_LIMIT,
RestrictedReasons.EXTERNAL.lower(), RestrictedReasons.EXTERNAL,
RestrictedReasons.FOTA.lower(), RestrictedReasons.FOTA,
RestrictedReasons.FROST.lower(), RestrictedReasons.FROST,
RestrictedReasons.NONE.lower(), RestrictedReasons.NONE,
RestrictedReasons.NOT_APPLICABLE.lower(), RestrictedReasons.NOT_APPLICABLE,
RestrictedReasons.PARK_OVERRIDE.lower(), RestrictedReasons.PARK_OVERRIDE,
RestrictedReasons.SENSOR.lower(), RestrictedReasons.SENSOR,
RestrictedReasons.WEEK_SCHEDULE.lower(), RestrictedReasons.WEEK_SCHEDULE,
] ]
STATE_NO_WORK_AREA_ACTIVE = "no_work_area_active" STATE_NO_WORK_AREA_ACTIVE = "no_work_area_active"
@ -272,15 +270,15 @@ MOWER_SENSOR_TYPES: tuple[AutomowerSensorEntityDescription, ...] = (
state_class=SensorStateClass.MEASUREMENT, state_class=SensorStateClass.MEASUREMENT,
device_class=SensorDeviceClass.BATTERY, device_class=SensorDeviceClass.BATTERY,
native_unit_of_measurement=PERCENTAGE, native_unit_of_measurement=PERCENTAGE,
value_fn=lambda data: data.battery.battery_percent, value_fn=attrgetter("battery.battery_percent"),
), ),
AutomowerSensorEntityDescription( AutomowerSensorEntityDescription(
key="mode", key="mode",
translation_key="mode", translation_key="mode",
device_class=SensorDeviceClass.ENUM, device_class=SensorDeviceClass.ENUM,
option_fn=lambda data: [option.lower() for option in list(MowerModes)], option_fn=lambda data: list(MowerModes),
value_fn=( value_fn=(
lambda data: data.mower.mode.lower() lambda data: data.mower.mode
if data.mower.mode != MowerModes.UNKNOWN if data.mower.mode != MowerModes.UNKNOWN
else None else None
), ),
@ -293,7 +291,7 @@ MOWER_SENSOR_TYPES: tuple[AutomowerSensorEntityDescription, ...] = (
native_unit_of_measurement=UnitOfTime.SECONDS, native_unit_of_measurement=UnitOfTime.SECONDS,
suggested_unit_of_measurement=UnitOfTime.HOURS, suggested_unit_of_measurement=UnitOfTime.HOURS,
exists_fn=lambda data: data.statistics.cutting_blade_usage_time is not None, exists_fn=lambda data: data.statistics.cutting_blade_usage_time is not None,
value_fn=lambda data: data.statistics.cutting_blade_usage_time, value_fn=attrgetter("statistics.cutting_blade_usage_time"),
), ),
AutomowerSensorEntityDescription( AutomowerSensorEntityDescription(
key="total_charging_time", key="total_charging_time",
@ -304,7 +302,7 @@ MOWER_SENSOR_TYPES: tuple[AutomowerSensorEntityDescription, ...] = (
native_unit_of_measurement=UnitOfTime.SECONDS, native_unit_of_measurement=UnitOfTime.SECONDS,
suggested_unit_of_measurement=UnitOfTime.HOURS, suggested_unit_of_measurement=UnitOfTime.HOURS,
exists_fn=lambda data: data.statistics.total_charging_time is not None, exists_fn=lambda data: data.statistics.total_charging_time is not None,
value_fn=lambda data: data.statistics.total_charging_time, value_fn=attrgetter("statistics.total_charging_time"),
), ),
AutomowerSensorEntityDescription( AutomowerSensorEntityDescription(
key="total_cutting_time", key="total_cutting_time",
@ -315,7 +313,7 @@ MOWER_SENSOR_TYPES: tuple[AutomowerSensorEntityDescription, ...] = (
native_unit_of_measurement=UnitOfTime.SECONDS, native_unit_of_measurement=UnitOfTime.SECONDS,
suggested_unit_of_measurement=UnitOfTime.HOURS, suggested_unit_of_measurement=UnitOfTime.HOURS,
exists_fn=lambda data: data.statistics.total_cutting_time is not None, exists_fn=lambda data: data.statistics.total_cutting_time is not None,
value_fn=lambda data: data.statistics.total_cutting_time, value_fn=attrgetter("statistics.total_cutting_time"),
), ),
AutomowerSensorEntityDescription( AutomowerSensorEntityDescription(
key="total_running_time", key="total_running_time",
@ -326,7 +324,7 @@ MOWER_SENSOR_TYPES: tuple[AutomowerSensorEntityDescription, ...] = (
native_unit_of_measurement=UnitOfTime.SECONDS, native_unit_of_measurement=UnitOfTime.SECONDS,
suggested_unit_of_measurement=UnitOfTime.HOURS, suggested_unit_of_measurement=UnitOfTime.HOURS,
exists_fn=lambda data: data.statistics.total_running_time is not None, exists_fn=lambda data: data.statistics.total_running_time is not None,
value_fn=lambda data: data.statistics.total_running_time, value_fn=attrgetter("statistics.total_running_time"),
), ),
AutomowerSensorEntityDescription( AutomowerSensorEntityDescription(
key="total_searching_time", key="total_searching_time",
@ -337,7 +335,7 @@ MOWER_SENSOR_TYPES: tuple[AutomowerSensorEntityDescription, ...] = (
native_unit_of_measurement=UnitOfTime.SECONDS, native_unit_of_measurement=UnitOfTime.SECONDS,
suggested_unit_of_measurement=UnitOfTime.HOURS, suggested_unit_of_measurement=UnitOfTime.HOURS,
exists_fn=lambda data: data.statistics.total_searching_time is not None, exists_fn=lambda data: data.statistics.total_searching_time is not None,
value_fn=lambda data: data.statistics.total_searching_time, value_fn=attrgetter("statistics.total_searching_time"),
), ),
AutomowerSensorEntityDescription( AutomowerSensorEntityDescription(
key="number_of_charging_cycles", key="number_of_charging_cycles",
@ -345,7 +343,7 @@ MOWER_SENSOR_TYPES: tuple[AutomowerSensorEntityDescription, ...] = (
entity_category=EntityCategory.DIAGNOSTIC, entity_category=EntityCategory.DIAGNOSTIC,
state_class=SensorStateClass.TOTAL, state_class=SensorStateClass.TOTAL,
exists_fn=lambda data: data.statistics.number_of_charging_cycles is not None, exists_fn=lambda data: data.statistics.number_of_charging_cycles is not None,
value_fn=lambda data: data.statistics.number_of_charging_cycles, value_fn=attrgetter("statistics.number_of_charging_cycles"),
), ),
AutomowerSensorEntityDescription( AutomowerSensorEntityDescription(
key="number_of_collisions", key="number_of_collisions",
@ -353,7 +351,7 @@ MOWER_SENSOR_TYPES: tuple[AutomowerSensorEntityDescription, ...] = (
entity_category=EntityCategory.DIAGNOSTIC, entity_category=EntityCategory.DIAGNOSTIC,
state_class=SensorStateClass.TOTAL, state_class=SensorStateClass.TOTAL,
exists_fn=lambda data: data.statistics.number_of_collisions is not None, exists_fn=lambda data: data.statistics.number_of_collisions is not None,
value_fn=lambda data: data.statistics.number_of_collisions, value_fn=attrgetter("statistics.number_of_collisions"),
), ),
AutomowerSensorEntityDescription( AutomowerSensorEntityDescription(
key="total_drive_distance", key="total_drive_distance",
@ -364,16 +362,13 @@ MOWER_SENSOR_TYPES: tuple[AutomowerSensorEntityDescription, ...] = (
native_unit_of_measurement=UnitOfLength.METERS, native_unit_of_measurement=UnitOfLength.METERS,
suggested_unit_of_measurement=UnitOfLength.KILOMETERS, suggested_unit_of_measurement=UnitOfLength.KILOMETERS,
exists_fn=lambda data: data.statistics.total_drive_distance is not None, exists_fn=lambda data: data.statistics.total_drive_distance is not None,
value_fn=lambda data: data.statistics.total_drive_distance, value_fn=attrgetter("statistics.total_drive_distance"),
), ),
AutomowerSensorEntityDescription( AutomowerSensorEntityDescription(
key="next_start_timestamp", key="next_start_timestamp",
translation_key="next_start_timestamp", translation_key="next_start_timestamp",
device_class=SensorDeviceClass.TIMESTAMP, device_class=SensorDeviceClass.TIMESTAMP,
value_fn=lambda data: naive_to_aware( value_fn=attrgetter("planner.next_start_datetime"),
data.planner.next_start_datetime_naive,
ZoneInfo(str(dt_util.DEFAULT_TIME_ZONE)),
),
), ),
AutomowerSensorEntityDescription( AutomowerSensorEntityDescription(
key="error", key="error",
@ -387,7 +382,7 @@ MOWER_SENSOR_TYPES: tuple[AutomowerSensorEntityDescription, ...] = (
translation_key="restricted_reason", translation_key="restricted_reason",
device_class=SensorDeviceClass.ENUM, device_class=SensorDeviceClass.ENUM,
option_fn=lambda data: RESTRICTED_REASONS, option_fn=lambda data: RESTRICTED_REASONS,
value_fn=lambda data: data.planner.restricted_reason.lower(), value_fn=attrgetter("planner.restricted_reason"),
), ),
AutomowerSensorEntityDescription( AutomowerSensorEntityDescription(
key="work_area", key="work_area",
@ -417,17 +412,14 @@ WORK_AREA_SENSOR_TYPES: tuple[WorkAreaSensorEntityDescription, ...] = (
exists_fn=lambda data: data.progress is not None, exists_fn=lambda data: data.progress is not None,
state_class=SensorStateClass.MEASUREMENT, state_class=SensorStateClass.MEASUREMENT,
native_unit_of_measurement=PERCENTAGE, native_unit_of_measurement=PERCENTAGE,
value_fn=lambda data: data.progress, value_fn=attrgetter("progress"),
), ),
WorkAreaSensorEntityDescription( WorkAreaSensorEntityDescription(
key="last_time_completed", key="last_time_completed",
translation_key_fn=_work_area_translation_key, translation_key_fn=_work_area_translation_key,
exists_fn=lambda data: data.last_time_completed_naive is not None, exists_fn=lambda data: data.last_time_completed is not None,
device_class=SensorDeviceClass.TIMESTAMP, device_class=SensorDeviceClass.TIMESTAMP,
value_fn=lambda data: naive_to_aware( value_fn=attrgetter("last_time_completed"),
data.last_time_completed_naive,
ZoneInfo(str(dt_util.DEFAULT_TIME_ZONE)),
),
), ),
) )

View File

@ -198,7 +198,7 @@ aioaseko==1.0.0
aioasuswrt==1.4.0 aioasuswrt==1.4.0
# homeassistant.components.husqvarna_automower # homeassistant.components.husqvarna_automower
aioautomower==2024.10.0 aioautomower==2024.10.3
# homeassistant.components.azure_devops # homeassistant.components.azure_devops
aioazuredevops==2.2.1 aioazuredevops==2.2.1

View File

@ -186,7 +186,7 @@ aioaseko==1.0.0
aioasuswrt==1.4.0 aioasuswrt==1.4.0
# homeassistant.components.husqvarna_automower # homeassistant.components.husqvarna_automower
aioautomower==2024.10.0 aioautomower==2024.10.3
# homeassistant.components.azure_devops # homeassistant.components.azure_devops
aioazuredevops==2.2.1 aioazuredevops==2.2.1

View File

@ -4,6 +4,7 @@ from collections.abc import Generator
import time import time
from unittest.mock import AsyncMock, patch from unittest.mock import AsyncMock, patch
from aioautomower.model import MowerAttributes
from aioautomower.session import AutomowerSession, _MowerCommands from aioautomower.session import AutomowerSession, _MowerCommands
from aioautomower.utils import mower_list_to_dictionary_dataclass from aioautomower.utils import mower_list_to_dictionary_dataclass
from aiohttp import ClientWebSocketResponse from aiohttp import ClientWebSocketResponse
@ -16,6 +17,7 @@ from homeassistant.components.application_credentials import (
from homeassistant.components.husqvarna_automower.const import DOMAIN from homeassistant.components.husqvarna_automower.const import DOMAIN
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.setup import async_setup_component from homeassistant.setup import async_setup_component
from homeassistant.util import dt as dt_util
from .const import CLIENT_ID, CLIENT_SECRET, USER_ID from .const import CLIENT_ID, CLIENT_SECRET, USER_ID
@ -40,6 +42,21 @@ def mock_scope() -> str:
return "iam:read amc:api" return "iam:read amc:api"
@pytest.fixture(name="mower_time_zone")
async def mock_time_zone(hass: HomeAssistant) -> dict[str, MowerAttributes]:
"""Fixture to set correct scope for the token."""
return await dt_util.async_get_time_zone("Europe/Berlin")
@pytest.fixture(name="values")
def mock_values(mower_time_zone) -> dict[str, MowerAttributes]:
"""Fixture to set correct scope for the token."""
return mower_list_to_dictionary_dataclass(
load_json_value_fixture("mower.json", DOMAIN),
mower_time_zone,
)
@pytest.fixture @pytest.fixture
def mock_config_entry(jwt: str, expires_at: int, scope: str) -> MockConfigEntry: def mock_config_entry(jwt: str, expires_at: int, scope: str) -> MockConfigEntry:
"""Return the default mocked config entry.""" """Return the default mocked config entry."""
@ -81,17 +98,13 @@ async def setup_credentials(hass: HomeAssistant) -> None:
@pytest.fixture @pytest.fixture
def mock_automower_client() -> Generator[AsyncMock]: def mock_automower_client(values) -> Generator[AsyncMock]:
"""Mock a Husqvarna Automower client.""" """Mock a Husqvarna Automower client."""
mower_dict = mower_list_to_dictionary_dataclass(
load_json_value_fixture("mower.json", DOMAIN)
)
mock = AsyncMock(spec=AutomowerSession) mock = AsyncMock(spec=AutomowerSession)
mock.auth = AsyncMock(side_effect=ClientWebSocketResponse) mock.auth = AsyncMock(side_effect=ClientWebSocketResponse)
mock.commands = AsyncMock(spec_set=_MowerCommands) mock.commands = AsyncMock(spec_set=_MowerCommands)
mock.get_status.return_value = mower_dict mock.get_status.return_value = values
with patch( with patch(
"homeassistant.components.husqvarna_automower.AutomowerSession", "homeassistant.components.husqvarna_automower.AutomowerSession",

View File

@ -68,6 +68,11 @@
'start': '2023-06-10T01:00:00+02:00', 'start': '2023-06-10T01:00:00+02:00',
'summary': 'Back lawn schedule 2', 'summary': 'Back lawn schedule 2',
}), }),
dict({
'end': '2023-06-12T09:00:00+02:00',
'start': '2023-06-12T01:00:00+02:00',
'summary': 'Back lawn schedule 2',
}),
]), ]),
}), }),
'calendar.test_mower_2': dict({ 'calendar.test_mower_2': dict({

View File

@ -68,31 +68,33 @@
'status_dateteime': '2023-06-05T00:00:00+00:00', 'status_dateteime': '2023-06-05T00:00:00+00:00',
}), }),
'mower': dict({ 'mower': dict({
'activity': 'PARKED_IN_CS', 'activity': 'parked_in_cs',
'error_code': 0, 'error_code': 0,
'error_datetime': None,
'error_datetime_naive': None, 'error_datetime_naive': None,
'error_key': None, 'error_key': None,
'error_timestamp': 0, 'error_timestamp': 0,
'inactive_reason': 'NONE', 'inactive_reason': 'none',
'is_error_confirmable': False, 'is_error_confirmable': False,
'mode': 'MAIN_AREA', 'mode': 'main_area',
'state': 'RESTRICTED', 'state': 'restricted',
'work_area_id': 123456, 'work_area_id': 123456,
'work_area_name': 'Front lawn', 'work_area_name': 'Front lawn',
}), }),
'planner': dict({ 'planner': dict({
'next_start': 1685991600000, 'next_start': 1685991600000,
'next_start_datetime': '2023-06-05T19:00:00+02:00',
'next_start_datetime_naive': '2023-06-05T19:00:00', 'next_start_datetime_naive': '2023-06-05T19:00:00',
'override': dict({ 'override': dict({
'action': 'NOT_ACTIVE', 'action': 'not_active',
}), }),
'restricted_reason': 'WEEK_SCHEDULE', 'restricted_reason': 'week_schedule',
}), }),
'positions': '**REDACTED**', 'positions': '**REDACTED**',
'settings': dict({ 'settings': dict({
'cutting_height': 4, 'cutting_height': 4,
'headlight': dict({ 'headlight': dict({
'mode': 'EVENING_ONLY', 'mode': 'evening_only',
}), }),
}), }),
'statistics': dict({ 'statistics': dict({
@ -138,6 +140,7 @@
'0': dict({ '0': dict({
'cutting_height': 50, 'cutting_height': 50,
'enabled': False, 'enabled': False,
'last_time_completed': '2024-08-12T05:07:49+02:00',
'last_time_completed_naive': '2024-08-12T05:07:49', 'last_time_completed_naive': '2024-08-12T05:07:49',
'name': 'my_lawn', 'name': 'my_lawn',
'progress': 20, 'progress': 20,
@ -145,6 +148,7 @@
'123456': dict({ '123456': dict({
'cutting_height': 50, 'cutting_height': 50,
'enabled': True, 'enabled': True,
'last_time_completed': '2024-08-12T07:54:29+02:00',
'last_time_completed_naive': '2024-08-12T07:54:29', 'last_time_completed_naive': '2024-08-12T07:54:29',
'name': 'Front lawn', 'name': 'Front lawn',
'progress': 40, 'progress': 40,
@ -152,6 +156,7 @@
'654321': dict({ '654321': dict({
'cutting_height': 25, 'cutting_height': 25,
'enabled': True, 'enabled': True,
'last_time_completed': None,
'last_time_completed_naive': None, 'last_time_completed_naive': None,
'name': 'Back lawn', 'name': 'Back lawn',
'progress': None, 'progress': None,
@ -165,7 +170,7 @@
'auth_implementation': 'husqvarna_automower', 'auth_implementation': 'husqvarna_automower',
'token': dict({ 'token': dict({
'access_token': '**REDACTED**', 'access_token': '**REDACTED**',
'expires_at': 1685926800.0, 'expires_at': 1685919600.0,
'expires_in': 86399, 'expires_in': 86399,
'provider': 'husqvarna', 'provider': 'husqvarna',
'refresh_token': '**REDACTED**', 'refresh_token': '**REDACTED**',

View File

@ -552,11 +552,11 @@
'area_id': None, 'area_id': None,
'capabilities': dict({ 'capabilities': dict({
'options': list([ 'options': list([
'main_area', <MowerModes.MAIN_AREA: 'main_area'>,
'demo', <MowerModes.DEMO: 'demo'>,
'secondary_area', <MowerModes.SECONDARY_AREA: 'secondary_area'>,
'home', <MowerModes.HOME: 'home'>,
'unknown', <MowerModes.UNKNOWN: 'unknown'>,
]), ]),
}), }),
'config_entry_id': <ANY>, 'config_entry_id': <ANY>,
@ -592,11 +592,11 @@
'device_class': 'enum', 'device_class': 'enum',
'friendly_name': 'Test Mower 1 Mode', 'friendly_name': 'Test Mower 1 Mode',
'options': list([ 'options': list([
'main_area', <MowerModes.MAIN_AREA: 'main_area'>,
'demo', <MowerModes.DEMO: 'demo'>,
'secondary_area', <MowerModes.SECONDARY_AREA: 'secondary_area'>,
'home', <MowerModes.HOME: 'home'>,
'unknown', <MowerModes.UNKNOWN: 'unknown'>,
]), ]),
}), }),
'context': <ANY>, 'context': <ANY>,
@ -856,16 +856,16 @@
'area_id': None, 'area_id': None,
'capabilities': dict({ 'capabilities': dict({
'options': list([ 'options': list([
'all_work_areas_completed', <RestrictedReasons.ALL_WORK_AREAS_COMPLETED: 'all_work_areas_completed'>,
'daily_limit', <RestrictedReasons.DAILY_LIMIT: 'daily_limit'>,
'external', <RestrictedReasons.EXTERNAL: 'external'>,
'fota', <RestrictedReasons.FOTA: 'fota'>,
'frost', <RestrictedReasons.FROST: 'frost'>,
'none', <RestrictedReasons.NONE: 'none'>,
'not_applicable', <RestrictedReasons.NOT_APPLICABLE: 'not_applicable'>,
'park_override', <RestrictedReasons.PARK_OVERRIDE: 'park_override'>,
'sensor', <RestrictedReasons.SENSOR: 'sensor'>,
'week_schedule', <RestrictedReasons.WEEK_SCHEDULE: 'week_schedule'>,
]), ]),
}), }),
'config_entry_id': <ANY>, 'config_entry_id': <ANY>,
@ -901,16 +901,16 @@
'device_class': 'enum', 'device_class': 'enum',
'friendly_name': 'Test Mower 1 Restricted reason', 'friendly_name': 'Test Mower 1 Restricted reason',
'options': list([ 'options': list([
'all_work_areas_completed', <RestrictedReasons.ALL_WORK_AREAS_COMPLETED: 'all_work_areas_completed'>,
'daily_limit', <RestrictedReasons.DAILY_LIMIT: 'daily_limit'>,
'external', <RestrictedReasons.EXTERNAL: 'external'>,
'fota', <RestrictedReasons.FOTA: 'fota'>,
'frost', <RestrictedReasons.FROST: 'frost'>,
'none', <RestrictedReasons.NONE: 'none'>,
'not_applicable', <RestrictedReasons.NOT_APPLICABLE: 'not_applicable'>,
'park_override', <RestrictedReasons.PARK_OVERRIDE: 'park_override'>,
'sensor', <RestrictedReasons.SENSOR: 'sensor'>,
'week_schedule', <RestrictedReasons.WEEK_SCHEDULE: 'week_schedule'>,
]), ]),
}), }),
'context': <ANY>, 'context': <ANY>,
@ -1658,11 +1658,11 @@
'area_id': None, 'area_id': None,
'capabilities': dict({ 'capabilities': dict({
'options': list([ 'options': list([
'main_area', <MowerModes.MAIN_AREA: 'main_area'>,
'demo', <MowerModes.DEMO: 'demo'>,
'secondary_area', <MowerModes.SECONDARY_AREA: 'secondary_area'>,
'home', <MowerModes.HOME: 'home'>,
'unknown', <MowerModes.UNKNOWN: 'unknown'>,
]), ]),
}), }),
'config_entry_id': <ANY>, 'config_entry_id': <ANY>,
@ -1698,11 +1698,11 @@
'device_class': 'enum', 'device_class': 'enum',
'friendly_name': 'Test Mower 2 Mode', 'friendly_name': 'Test Mower 2 Mode',
'options': list([ 'options': list([
'main_area', <MowerModes.MAIN_AREA: 'main_area'>,
'demo', <MowerModes.DEMO: 'demo'>,
'secondary_area', <MowerModes.SECONDARY_AREA: 'secondary_area'>,
'home', <MowerModes.HOME: 'home'>,
'unknown', <MowerModes.UNKNOWN: 'unknown'>,
]), ]),
}), }),
'context': <ANY>, 'context': <ANY>,
@ -1767,16 +1767,16 @@
'area_id': None, 'area_id': None,
'capabilities': dict({ 'capabilities': dict({
'options': list([ 'options': list([
'all_work_areas_completed', <RestrictedReasons.ALL_WORK_AREAS_COMPLETED: 'all_work_areas_completed'>,
'daily_limit', <RestrictedReasons.DAILY_LIMIT: 'daily_limit'>,
'external', <RestrictedReasons.EXTERNAL: 'external'>,
'fota', <RestrictedReasons.FOTA: 'fota'>,
'frost', <RestrictedReasons.FROST: 'frost'>,
'none', <RestrictedReasons.NONE: 'none'>,
'not_applicable', <RestrictedReasons.NOT_APPLICABLE: 'not_applicable'>,
'park_override', <RestrictedReasons.PARK_OVERRIDE: 'park_override'>,
'sensor', <RestrictedReasons.SENSOR: 'sensor'>,
'week_schedule', <RestrictedReasons.WEEK_SCHEDULE: 'week_schedule'>,
]), ]),
}), }),
'config_entry_id': <ANY>, 'config_entry_id': <ANY>,
@ -1812,16 +1812,16 @@
'device_class': 'enum', 'device_class': 'enum',
'friendly_name': 'Test Mower 2 Restricted reason', 'friendly_name': 'Test Mower 2 Restricted reason',
'options': list([ 'options': list([
'all_work_areas_completed', <RestrictedReasons.ALL_WORK_AREAS_COMPLETED: 'all_work_areas_completed'>,
'daily_limit', <RestrictedReasons.DAILY_LIMIT: 'daily_limit'>,
'external', <RestrictedReasons.EXTERNAL: 'external'>,
'fota', <RestrictedReasons.FOTA: 'fota'>,
'frost', <RestrictedReasons.FROST: 'frost'>,
'none', <RestrictedReasons.NONE: 'none'>,
'not_applicable', <RestrictedReasons.NOT_APPLICABLE: 'not_applicable'>,
'park_override', <RestrictedReasons.PARK_OVERRIDE: 'park_override'>,
'sensor', <RestrictedReasons.SENSOR: 'sensor'>,
'week_schedule', <RestrictedReasons.WEEK_SCHEDULE: 'week_schedule'>,
]), ]),
}), }),
'context': <ANY>, 'context': <ANY>,

View File

@ -2,12 +2,10 @@
from unittest.mock import AsyncMock, patch from unittest.mock import AsyncMock, patch
from aioautomower.model import MowerActivities from aioautomower.model import MowerActivities, MowerAttributes
from aioautomower.utils import mower_list_to_dictionary_dataclass
from freezegun.api import FrozenDateTimeFactory from freezegun.api import FrozenDateTimeFactory
from syrupy import SnapshotAssertion from syrupy import SnapshotAssertion
from homeassistant.components.husqvarna_automower.const import DOMAIN
from homeassistant.components.husqvarna_automower.coordinator import SCAN_INTERVAL from homeassistant.components.husqvarna_automower.coordinator import SCAN_INTERVAL
from homeassistant.const import Platform from homeassistant.const import Platform
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
@ -16,12 +14,7 @@ from homeassistant.helpers import entity_registry as er
from . import setup_integration from . import setup_integration
from .const import TEST_MOWER_ID from .const import TEST_MOWER_ID
from tests.common import ( from tests.common import MockConfigEntry, async_fire_time_changed, snapshot_platform
MockConfigEntry,
async_fire_time_changed,
load_json_value_fixture,
snapshot_platform,
)
async def test_binary_sensor_states( async def test_binary_sensor_states(
@ -29,11 +22,9 @@ async def test_binary_sensor_states(
mock_automower_client: AsyncMock, mock_automower_client: AsyncMock,
mock_config_entry: MockConfigEntry, mock_config_entry: MockConfigEntry,
freezer: FrozenDateTimeFactory, freezer: FrozenDateTimeFactory,
values: dict[str, MowerAttributes],
) -> None: ) -> None:
"""Test binary sensor states.""" """Test binary sensor states."""
values = mower_list_to_dictionary_dataclass(
load_json_value_fixture("mower.json", DOMAIN)
)
await setup_integration(hass, mock_config_entry) await setup_integration(hass, mock_config_entry)
state = hass.states.get("binary_sensor.test_mower_1_charging") state = hass.states.get("binary_sensor.test_mower_1_charging")
assert state is not None assert state is not None

View File

@ -2,16 +2,14 @@
import datetime import datetime
from unittest.mock import AsyncMock, patch from unittest.mock import AsyncMock, patch
import zoneinfo
from aioautomower.exceptions import ApiException from aioautomower.exceptions import ApiException
from aioautomower.utils import mower_list_to_dictionary_dataclass from aioautomower.model import MowerAttributes
from freezegun.api import FrozenDateTimeFactory from freezegun.api import FrozenDateTimeFactory
import pytest import pytest
from syrupy import SnapshotAssertion from syrupy import SnapshotAssertion
from homeassistant.components.button import DOMAIN as BUTTON_DOMAIN, SERVICE_PRESS from homeassistant.components.button import DOMAIN as BUTTON_DOMAIN, SERVICE_PRESS
from homeassistant.components.husqvarna_automower.const import DOMAIN
from homeassistant.components.husqvarna_automower.coordinator import SCAN_INTERVAL from homeassistant.components.husqvarna_automower.coordinator import SCAN_INTERVAL
from homeassistant.const import ( from homeassistant.const import (
ATTR_ENTITY_ID, ATTR_ENTITY_ID,
@ -26,12 +24,7 @@ from homeassistant.helpers import entity_registry as er
from . import setup_integration from . import setup_integration
from .const import TEST_MOWER_ID from .const import TEST_MOWER_ID
from tests.common import ( from tests.common import MockConfigEntry, async_fire_time_changed, snapshot_platform
MockConfigEntry,
async_fire_time_changed,
load_json_value_fixture,
snapshot_platform,
)
@pytest.mark.freeze_time(datetime.datetime(2023, 6, 5, tzinfo=datetime.UTC)) @pytest.mark.freeze_time(datetime.datetime(2023, 6, 5, tzinfo=datetime.UTC))
@ -40,6 +33,7 @@ async def test_button_states_and_commands(
mock_automower_client: AsyncMock, mock_automower_client: AsyncMock,
mock_config_entry: MockConfigEntry, mock_config_entry: MockConfigEntry,
freezer: FrozenDateTimeFactory, freezer: FrozenDateTimeFactory,
values: dict[str, MowerAttributes],
) -> None: ) -> None:
"""Test error confirm button command.""" """Test error confirm button command."""
entity_id = "button.test_mower_1_confirm_error" entity_id = "button.test_mower_1_confirm_error"
@ -48,9 +42,6 @@ async def test_button_states_and_commands(
assert state.name == "Test Mower 1 Confirm error" assert state.name == "Test Mower 1 Confirm error"
assert state.state == STATE_UNAVAILABLE assert state.state == STATE_UNAVAILABLE
values = mower_list_to_dictionary_dataclass(
load_json_value_fixture("mower.json", DOMAIN)
)
values[TEST_MOWER_ID].mower.is_error_confirmable = None values[TEST_MOWER_ID].mower.is_error_confirmable = None
mock_automower_client.get_status.return_value = values mock_automower_client.get_status.return_value = values
freezer.tick(SCAN_INTERVAL) freezer.tick(SCAN_INTERVAL)
@ -99,6 +90,7 @@ async def test_sync_clock(
mock_automower_client: AsyncMock, mock_automower_client: AsyncMock,
mock_config_entry: MockConfigEntry, mock_config_entry: MockConfigEntry,
freezer: FrozenDateTimeFactory, freezer: FrozenDateTimeFactory,
values: dict[str, MowerAttributes],
) -> None: ) -> None:
"""Test sync clock button command.""" """Test sync clock button command."""
entity_id = "button.test_mower_1_sync_clock" entity_id = "button.test_mower_1_sync_clock"
@ -106,9 +98,6 @@ async def test_sync_clock(
state = hass.states.get(entity_id) state = hass.states.get(entity_id)
assert state.name == "Test Mower 1 Sync clock" assert state.name == "Test Mower 1 Sync clock"
values = mower_list_to_dictionary_dataclass(
load_json_value_fixture("mower.json", DOMAIN)
)
mock_automower_client.get_status.return_value = values mock_automower_client.get_status.return_value = values
await hass.services.async_call( await hass.services.async_call(
@ -118,12 +107,7 @@ async def test_sync_clock(
blocking=True, blocking=True,
) )
mocked_method = mock_automower_client.commands.set_datetime mocked_method = mock_automower_client.commands.set_datetime
# datetime(2024, 2, 29, 11, tzinfo=datetime.UTC) is in local time of the tests mocked_method.assert_called_once_with(TEST_MOWER_ID)
# datetime(2024, 2, 29, 12, tzinfo=zoneinfo.ZoneInfo(key='Europe/Berlin'))
mocked_method.assert_called_once_with(
TEST_MOWER_ID,
datetime.datetime(2024, 2, 29, 12, tzinfo=zoneinfo.ZoneInfo("Europe/Berlin")),
)
await hass.async_block_till_done() await hass.async_block_till_done()
state = hass.states.get(entity_id) state = hass.states.get(entity_id)
assert state.state == "2024-02-29T11:00:00+00:00" assert state.state == "2024-02-29T11:00:00+00:00"

View File

@ -6,6 +6,7 @@ from http import HTTPStatus
from typing import Any from typing import Any
from unittest.mock import AsyncMock from unittest.mock import AsyncMock
import urllib import urllib
import zoneinfo
from aioautomower.utils import mower_list_to_dictionary_dataclass from aioautomower.utils import mower_list_to_dictionary_dataclass
from freezegun.api import FrozenDateTimeFactory from freezegun.api import FrozenDateTimeFactory
@ -93,12 +94,16 @@ async def test_empty_calendar(
mock_config_entry: MockConfigEntry, mock_config_entry: MockConfigEntry,
freezer: FrozenDateTimeFactory, freezer: FrozenDateTimeFactory,
get_events: GetEventsFn, get_events: GetEventsFn,
mower_time_zone: zoneinfo.ZoneInfo,
) -> None: ) -> None:
"""State if there is no schedule set.""" """State if there is no schedule set."""
await setup_integration(hass, mock_config_entry) await setup_integration(hass, mock_config_entry)
json_values = load_json_value_fixture("mower.json", DOMAIN) json_values = load_json_value_fixture("mower.json", DOMAIN)
json_values["data"][0]["attributes"]["calendar"]["tasks"] = [] json_values["data"][0]["attributes"]["calendar"]["tasks"] = []
values = mower_list_to_dictionary_dataclass(json_values) values = mower_list_to_dictionary_dataclass(
json_values,
mower_time_zone,
)
mock_automower_client.get_status.return_value = values mock_automower_client.get_status.return_value = values
freezer.tick(SCAN_INTERVAL) freezer.tick(SCAN_INTERVAL)
async_fire_time_changed(hass) async_fire_time_changed(hass)

View File

@ -2,6 +2,7 @@
import datetime import datetime
from unittest.mock import AsyncMock from unittest.mock import AsyncMock
import zoneinfo
import pytest import pytest
from syrupy.assertion import SnapshotAssertion from syrupy.assertion import SnapshotAssertion
@ -21,7 +22,9 @@ from tests.components.diagnostics import (
from tests.typing import ClientSessionGenerator from tests.typing import ClientSessionGenerator
@pytest.mark.freeze_time(datetime.datetime(2023, 6, 5, tzinfo=datetime.UTC)) @pytest.mark.freeze_time(
datetime.datetime(2023, 6, 5, tzinfo=zoneinfo.ZoneInfo("Europe/Berlin"))
)
async def test_entry_diagnostics( async def test_entry_diagnostics(
hass: HomeAssistant, hass: HomeAssistant,
hass_client: ClientSessionGenerator, hass_client: ClientSessionGenerator,
@ -40,7 +43,9 @@ async def test_entry_diagnostics(
assert result == snapshot(exclude=props("created_at", "modified_at")) assert result == snapshot(exclude=props("created_at", "modified_at"))
@pytest.mark.freeze_time(datetime.datetime(2023, 6, 5, tzinfo=datetime.UTC)) @pytest.mark.freeze_time(
datetime.datetime(2023, 6, 5, tzinfo=zoneinfo.ZoneInfo("Europe/Berlin"))
)
async def test_device_diagnostics( async def test_device_diagnostics(
hass: HomeAssistant, hass: HomeAssistant,
hass_client: ClientSessionGenerator, hass_client: ClientSessionGenerator,

View File

@ -10,7 +10,7 @@ from aioautomower.exceptions import (
AuthException, AuthException,
HusqvarnaWSServerHandshakeError, HusqvarnaWSServerHandshakeError,
) )
from aioautomower.utils import mower_list_to_dictionary_dataclass from aioautomower.model import MowerAttributes
from freezegun.api import FrozenDateTimeFactory from freezegun.api import FrozenDateTimeFactory
import pytest import pytest
from syrupy.assertion import SnapshotAssertion from syrupy.assertion import SnapshotAssertion
@ -23,11 +23,7 @@ from homeassistant.helpers import device_registry as dr, entity_registry as er
from . import setup_integration from . import setup_integration
from .const import TEST_MOWER_ID from .const import TEST_MOWER_ID
from tests.common import ( from tests.common import MockConfigEntry, async_fire_time_changed
MockConfigEntry,
async_fire_time_changed,
load_json_value_fixture,
)
from tests.test_util.aiohttp import AiohttpClientMocker from tests.test_util.aiohttp import AiohttpClientMocker
@ -172,12 +168,10 @@ async def test_workarea_deleted(
mock_automower_client: AsyncMock, mock_automower_client: AsyncMock,
mock_config_entry: MockConfigEntry, mock_config_entry: MockConfigEntry,
entity_registry: er.EntityRegistry, entity_registry: er.EntityRegistry,
values: dict[str, MowerAttributes],
) -> None: ) -> None:
"""Test if work area is deleted after removed.""" """Test if work area is deleted after removed."""
values = mower_list_to_dictionary_dataclass(
load_json_value_fixture("mower.json", DOMAIN)
)
await setup_integration(hass, mock_config_entry) await setup_integration(hass, mock_config_entry)
current_entries = len( current_entries = len(
er.async_entries_for_config_entry(entity_registry, mock_config_entry.entry_id) er.async_entries_for_config_entry(entity_registry, mock_config_entry.entry_id)
@ -198,6 +192,7 @@ async def test_coordinator_automatic_registry_cleanup(
mock_config_entry: MockConfigEntry, mock_config_entry: MockConfigEntry,
device_registry: dr.DeviceRegistry, device_registry: dr.DeviceRegistry,
entity_registry: er.EntityRegistry, entity_registry: er.EntityRegistry,
values: dict[str, MowerAttributes],
) -> None: ) -> None:
"""Test automatic registry cleanup.""" """Test automatic registry cleanup."""
await setup_integration(hass, mock_config_entry) await setup_integration(hass, mock_config_entry)
@ -211,9 +206,6 @@ async def test_coordinator_automatic_registry_cleanup(
dr.async_entries_for_config_entry(device_registry, entry.entry_id) dr.async_entries_for_config_entry(device_registry, entry.entry_id)
) )
values = mower_list_to_dictionary_dataclass(
load_json_value_fixture("mower.json", DOMAIN)
)
values.pop(TEST_MOWER_ID) values.pop(TEST_MOWER_ID)
mock_automower_client.get_status.return_value = values mock_automower_client.get_status.return_value = values
await hass.config_entries.async_reload(mock_config_entry.entry_id) await hass.config_entries.async_reload(mock_config_entry.entry_id)

View File

@ -4,7 +4,7 @@ from datetime import timedelta
from unittest.mock import AsyncMock from unittest.mock import AsyncMock
from aioautomower.exceptions import ApiException from aioautomower.exceptions import ApiException
from aioautomower.utils import mower_list_to_dictionary_dataclass from aioautomower.model import MowerActivities, MowerAttributes, MowerStates
from freezegun.api import FrozenDateTimeFactory from freezegun.api import FrozenDateTimeFactory
import pytest import pytest
from voluptuous.error import MultipleInvalid from voluptuous.error import MultipleInvalid
@ -18,11 +18,7 @@ from homeassistant.exceptions import HomeAssistantError, ServiceValidationError
from . import setup_integration from . import setup_integration
from .const import TEST_MOWER_ID from .const import TEST_MOWER_ID
from tests.common import ( from tests.common import MockConfigEntry, async_fire_time_changed
MockConfigEntry,
async_fire_time_changed,
load_json_value_fixture,
)
async def test_lawn_mower_states( async def test_lawn_mower_states(
@ -30,21 +26,23 @@ async def test_lawn_mower_states(
mock_automower_client: AsyncMock, mock_automower_client: AsyncMock,
mock_config_entry: MockConfigEntry, mock_config_entry: MockConfigEntry,
freezer: FrozenDateTimeFactory, freezer: FrozenDateTimeFactory,
values: dict[str, MowerAttributes],
) -> None: ) -> None:
"""Test lawn_mower state.""" """Test lawn_mower state."""
values = mower_list_to_dictionary_dataclass(
load_json_value_fixture("mower.json", DOMAIN)
)
await setup_integration(hass, mock_config_entry) await setup_integration(hass, mock_config_entry)
state = hass.states.get("lawn_mower.test_mower_1") state = hass.states.get("lawn_mower.test_mower_1")
assert state is not None assert state is not None
assert state.state == LawnMowerActivity.DOCKED assert state.state == LawnMowerActivity.DOCKED
for activity, state, expected_state in ( for activity, state, expected_state in (
("UNKNOWN", "PAUSED", LawnMowerActivity.PAUSED), (MowerActivities.UNKNOWN, MowerStates.PAUSED, LawnMowerActivity.PAUSED),
("MOWING", "NOT_APPLICABLE", LawnMowerActivity.MOWING), (MowerActivities.MOWING, MowerStates.NOT_APPLICABLE, LawnMowerActivity.MOWING),
("NOT_APPLICABLE", "ERROR", LawnMowerActivity.ERROR), (MowerActivities.NOT_APPLICABLE, MowerStates.ERROR, LawnMowerActivity.ERROR),
("GOING_HOME", "IN_OPERATION", LawnMowerActivity.RETURNING), (
MowerActivities.GOING_HOME,
MowerStates.IN_OPERATION,
LawnMowerActivity.RETURNING,
),
): ):
values[TEST_MOWER_ID].mower.activity = activity values[TEST_MOWER_ID].mower.activity = activity
values[TEST_MOWER_ID].mower.state = state values[TEST_MOWER_ID].mower.state = state
@ -253,12 +251,10 @@ async def test_lawn_mower_wrong_service_commands(
mock_automower_client: AsyncMock, mock_automower_client: AsyncMock,
mock_config_entry: MockConfigEntry, mock_config_entry: MockConfigEntry,
freezer: FrozenDateTimeFactory, freezer: FrozenDateTimeFactory,
values: dict[str, MowerAttributes],
) -> None: ) -> None:
"""Test lawn_mower commands.""" """Test lawn_mower commands."""
await setup_integration(hass, mock_config_entry) await setup_integration(hass, mock_config_entry)
values = mower_list_to_dictionary_dataclass(
load_json_value_fixture("mower.json", DOMAIN)
)
values[TEST_MOWER_ID].capabilities.work_areas = mower_support_wa values[TEST_MOWER_ID].capabilities.work_areas = mower_support_wa
mock_automower_client.get_status.return_value = values mock_automower_client.get_status.return_value = values
freezer.tick(SCAN_INTERVAL) freezer.tick(SCAN_INTERVAL)

View File

@ -4,15 +4,12 @@ from datetime import timedelta
from unittest.mock import AsyncMock, patch from unittest.mock import AsyncMock, patch
from aioautomower.exceptions import ApiException from aioautomower.exceptions import ApiException
from aioautomower.utils import mower_list_to_dictionary_dataclass from aioautomower.model import MowerAttributes
from freezegun.api import FrozenDateTimeFactory from freezegun.api import FrozenDateTimeFactory
import pytest import pytest
from syrupy import SnapshotAssertion from syrupy import SnapshotAssertion
from homeassistant.components.husqvarna_automower.const import ( from homeassistant.components.husqvarna_automower.const import EXECUTION_TIME_DELAY
DOMAIN,
EXECUTION_TIME_DELAY,
)
from homeassistant.const import Platform from homeassistant.const import Platform
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError from homeassistant.exceptions import HomeAssistantError
@ -21,12 +18,7 @@ from homeassistant.helpers import entity_registry as er
from . import setup_integration from . import setup_integration
from .const import TEST_MOWER_ID from .const import TEST_MOWER_ID
from tests.common import ( from tests.common import MockConfigEntry, async_fire_time_changed, snapshot_platform
MockConfigEntry,
async_fire_time_changed,
load_json_value_fixture,
snapshot_platform,
)
@pytest.mark.usefixtures("entity_registry_enabled_by_default") @pytest.mark.usefixtures("entity_registry_enabled_by_default")
@ -68,13 +60,11 @@ async def test_number_workarea_commands(
mock_automower_client: AsyncMock, mock_automower_client: AsyncMock,
mock_config_entry: MockConfigEntry, mock_config_entry: MockConfigEntry,
freezer: FrozenDateTimeFactory, freezer: FrozenDateTimeFactory,
values: dict[str, MowerAttributes],
) -> None: ) -> None:
"""Test number commands.""" """Test number commands."""
entity_id = "number.test_mower_1_front_lawn_cutting_height" entity_id = "number.test_mower_1_front_lawn_cutting_height"
await setup_integration(hass, mock_config_entry) await setup_integration(hass, mock_config_entry)
values = mower_list_to_dictionary_dataclass(
load_json_value_fixture("mower.json", DOMAIN)
)
values[TEST_MOWER_ID].work_areas[123456].cutting_height = 75 values[TEST_MOWER_ID].work_areas[123456].cutting_height = 75
mock_automower_client.get_status.return_value = values mock_automower_client.get_status.return_value = values
mocked_method = AsyncMock() mocked_method = AsyncMock()

View File

@ -3,12 +3,10 @@
from unittest.mock import AsyncMock from unittest.mock import AsyncMock
from aioautomower.exceptions import ApiException from aioautomower.exceptions import ApiException
from aioautomower.model import HeadlightModes from aioautomower.model import HeadlightModes, MowerAttributes
from aioautomower.utils import mower_list_to_dictionary_dataclass
from freezegun.api import FrozenDateTimeFactory from freezegun.api import FrozenDateTimeFactory
import pytest import pytest
from homeassistant.components.husqvarna_automower.const import DOMAIN
from homeassistant.components.husqvarna_automower.coordinator import SCAN_INTERVAL from homeassistant.components.husqvarna_automower.coordinator import SCAN_INTERVAL
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError from homeassistant.exceptions import HomeAssistantError
@ -16,11 +14,7 @@ from homeassistant.exceptions import HomeAssistantError
from . import setup_integration from . import setup_integration
from .const import TEST_MOWER_ID from .const import TEST_MOWER_ID
from tests.common import ( from tests.common import MockConfigEntry, async_fire_time_changed
MockConfigEntry,
async_fire_time_changed,
load_json_value_fixture,
)
async def test_select_states( async def test_select_states(
@ -28,11 +22,9 @@ async def test_select_states(
mock_automower_client: AsyncMock, mock_automower_client: AsyncMock,
mock_config_entry: MockConfigEntry, mock_config_entry: MockConfigEntry,
freezer: FrozenDateTimeFactory, freezer: FrozenDateTimeFactory,
values: dict[str, MowerAttributes],
) -> None: ) -> None:
"""Test states of headlight mode select.""" """Test states of headlight mode select."""
values = mower_list_to_dictionary_dataclass(
load_json_value_fixture("mower.json", DOMAIN)
)
await setup_integration(hass, mock_config_entry) await setup_integration(hass, mock_config_entry)
state = hass.states.get("select.test_mower_1_headlight_mode") state = hass.states.get("select.test_mower_1_headlight_mode")
assert state is not None assert state is not None

View File

@ -1,14 +1,14 @@
"""Tests for sensor platform.""" """Tests for sensor platform."""
import datetime
from unittest.mock import AsyncMock, patch from unittest.mock import AsyncMock, patch
import zoneinfo
from aioautomower.model import MowerModes, MowerStates from aioautomower.model import MowerAttributes, MowerModes, MowerStates
from aioautomower.utils import mower_list_to_dictionary_dataclass
from freezegun.api import FrozenDateTimeFactory from freezegun.api import FrozenDateTimeFactory
import pytest import pytest
from syrupy import SnapshotAssertion from syrupy import SnapshotAssertion
from homeassistant.components.husqvarna_automower.const import DOMAIN
from homeassistant.components.husqvarna_automower.coordinator import SCAN_INTERVAL from homeassistant.components.husqvarna_automower.coordinator import SCAN_INTERVAL
from homeassistant.const import STATE_UNKNOWN, Platform from homeassistant.const import STATE_UNKNOWN, Platform
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
@ -17,12 +17,7 @@ from homeassistant.helpers import entity_registry as er
from . import setup_integration from . import setup_integration
from .const import TEST_MOWER_ID from .const import TEST_MOWER_ID
from tests.common import ( from tests.common import MockConfigEntry, async_fire_time_changed, snapshot_platform
MockConfigEntry,
async_fire_time_changed,
load_json_value_fixture,
snapshot_platform,
)
async def test_sensor_unknown_states( async def test_sensor_unknown_states(
@ -30,11 +25,9 @@ async def test_sensor_unknown_states(
mock_automower_client: AsyncMock, mock_automower_client: AsyncMock,
mock_config_entry: MockConfigEntry, mock_config_entry: MockConfigEntry,
freezer: FrozenDateTimeFactory, freezer: FrozenDateTimeFactory,
values: dict[str, MowerAttributes],
) -> None: ) -> None:
"""Test a sensor which returns unknown.""" """Test a sensor which returns unknown."""
values = mower_list_to_dictionary_dataclass(
load_json_value_fixture("mower.json", DOMAIN)
)
await setup_integration(hass, mock_config_entry) await setup_integration(hass, mock_config_entry)
state = hass.states.get("sensor.test_mower_1_mode") state = hass.states.get("sensor.test_mower_1_mode")
assert state is not None assert state is not None
@ -63,11 +56,15 @@ async def test_cutting_blade_usage_time_sensor(
assert state.state == "0.034" assert state.state == "0.034"
@pytest.mark.freeze_time(
datetime.datetime(2023, 6, 5, tzinfo=zoneinfo.ZoneInfo("Europe/Berlin"))
)
async def test_next_start_sensor( async def test_next_start_sensor(
hass: HomeAssistant, hass: HomeAssistant,
mock_automower_client: AsyncMock, mock_automower_client: AsyncMock,
mock_config_entry: MockConfigEntry, mock_config_entry: MockConfigEntry,
freezer: FrozenDateTimeFactory, freezer: FrozenDateTimeFactory,
values: dict[str, MowerAttributes],
) -> None: ) -> None:
"""Test if this sensor is only added, if data is available.""" """Test if this sensor is only added, if data is available."""
await setup_integration(hass, mock_config_entry) await setup_integration(hass, mock_config_entry)
@ -75,10 +72,7 @@ async def test_next_start_sensor(
assert state is not None assert state is not None
assert state.state == "2023-06-05T17:00:00+00:00" assert state.state == "2023-06-05T17:00:00+00:00"
values = mower_list_to_dictionary_dataclass( values[TEST_MOWER_ID].planner.next_start_datetime = None
load_json_value_fixture("mower.json", DOMAIN)
)
values[TEST_MOWER_ID].planner.next_start_datetime_naive = None
mock_automower_client.get_status.return_value = values mock_automower_client.get_status.return_value = values
freezer.tick(SCAN_INTERVAL) freezer.tick(SCAN_INTERVAL)
async_fire_time_changed(hass) async_fire_time_changed(hass)
@ -92,6 +86,7 @@ async def test_work_area_sensor(
mock_automower_client: AsyncMock, mock_automower_client: AsyncMock,
mock_config_entry: MockConfigEntry, mock_config_entry: MockConfigEntry,
freezer: FrozenDateTimeFactory, freezer: FrozenDateTimeFactory,
values: dict[str, MowerAttributes],
) -> None: ) -> None:
"""Test the work area sensor.""" """Test the work area sensor."""
await setup_integration(hass, mock_config_entry) await setup_integration(hass, mock_config_entry)
@ -99,9 +94,6 @@ async def test_work_area_sensor(
assert state is not None assert state is not None
assert state.state == "Front lawn" assert state.state == "Front lawn"
values = mower_list_to_dictionary_dataclass(
load_json_value_fixture("mower.json", DOMAIN)
)
values[TEST_MOWER_ID].mower.work_area_id = None values[TEST_MOWER_ID].mower.work_area_id = None
mock_automower_client.get_status.return_value = values mock_automower_client.get_status.return_value = values
freezer.tick(SCAN_INTERVAL) freezer.tick(SCAN_INTERVAL)
@ -137,13 +129,10 @@ async def test_statistics_not_available(
mock_automower_client: AsyncMock, mock_automower_client: AsyncMock,
mock_config_entry: MockConfigEntry, mock_config_entry: MockConfigEntry,
sensor_to_test: str, sensor_to_test: str,
values: dict[str, MowerAttributes],
) -> None: ) -> None:
"""Test if this sensor is only added, if data is available.""" """Test if this sensor is only added, if data is available."""
values = mower_list_to_dictionary_dataclass(
load_json_value_fixture("mower.json", DOMAIN)
)
delattr(values[TEST_MOWER_ID].statistics, sensor_to_test) delattr(values[TEST_MOWER_ID].statistics, sensor_to_test)
mock_automower_client.get_status.return_value = values mock_automower_client.get_status.return_value = values
await setup_integration(hass, mock_config_entry) await setup_integration(hass, mock_config_entry)
@ -156,11 +145,9 @@ async def test_error_sensor(
mock_automower_client: AsyncMock, mock_automower_client: AsyncMock,
mock_config_entry: MockConfigEntry, mock_config_entry: MockConfigEntry,
freezer: FrozenDateTimeFactory, freezer: FrozenDateTimeFactory,
values: dict[str, MowerAttributes],
) -> None: ) -> None:
"""Test error sensor.""" """Test error sensor."""
values = mower_list_to_dictionary_dataclass(
load_json_value_fixture("mower.json", DOMAIN)
)
await setup_integration(hass, mock_config_entry) await setup_integration(hass, mock_config_entry)
for state, error_key, expected_state in ( for state, error_key, expected_state in (

View File

@ -2,9 +2,10 @@
from datetime import timedelta from datetime import timedelta
from unittest.mock import AsyncMock, patch from unittest.mock import AsyncMock, patch
import zoneinfo
from aioautomower.exceptions import ApiException from aioautomower.exceptions import ApiException
from aioautomower.model import MowerModes from aioautomower.model import MowerAttributes, MowerModes
from aioautomower.utils import mower_list_to_dictionary_dataclass from aioautomower.utils import mower_list_to_dictionary_dataclass
from freezegun.api import FrozenDateTimeFactory from freezegun.api import FrozenDateTimeFactory
import pytest import pytest
@ -46,11 +47,9 @@ async def test_switch_states(
mock_automower_client: AsyncMock, mock_automower_client: AsyncMock,
mock_config_entry: MockConfigEntry, mock_config_entry: MockConfigEntry,
freezer: FrozenDateTimeFactory, freezer: FrozenDateTimeFactory,
values: dict[str, MowerAttributes],
) -> None: ) -> None:
"""Test switch state.""" """Test switch state."""
values = mower_list_to_dictionary_dataclass(
load_json_value_fixture("mower.json", DOMAIN)
)
await setup_integration(hass, mock_config_entry) await setup_integration(hass, mock_config_entry)
for mode, expected_state in ( for mode, expected_state in (
@ -122,12 +121,14 @@ async def test_stay_out_zone_switch_commands(
mock_automower_client: AsyncMock, mock_automower_client: AsyncMock,
mock_config_entry: MockConfigEntry, mock_config_entry: MockConfigEntry,
freezer: FrozenDateTimeFactory, freezer: FrozenDateTimeFactory,
mower_time_zone: zoneinfo.ZoneInfo,
) -> None: ) -> None:
"""Test switch commands.""" """Test switch commands."""
entity_id = "switch.test_mower_1_avoid_danger_zone" entity_id = "switch.test_mower_1_avoid_danger_zone"
await setup_integration(hass, mock_config_entry) await setup_integration(hass, mock_config_entry)
values = mower_list_to_dictionary_dataclass( values = mower_list_to_dictionary_dataclass(
load_json_value_fixture("mower.json", DOMAIN) load_json_value_fixture("mower.json", DOMAIN),
mower_time_zone,
) )
values[TEST_MOWER_ID].stay_out_zones.zones[TEST_ZONE_ID].enabled = boolean values[TEST_MOWER_ID].stay_out_zones.zones[TEST_ZONE_ID].enabled = boolean
mock_automower_client.get_status.return_value = values mock_automower_client.get_status.return_value = values
@ -177,12 +178,14 @@ async def test_work_area_switch_commands(
mock_automower_client: AsyncMock, mock_automower_client: AsyncMock,
mock_config_entry: MockConfigEntry, mock_config_entry: MockConfigEntry,
freezer: FrozenDateTimeFactory, freezer: FrozenDateTimeFactory,
mower_time_zone: zoneinfo.ZoneInfo,
) -> None: ) -> None:
"""Test switch commands.""" """Test switch commands."""
entity_id = "switch.test_mower_1_my_lawn" entity_id = "switch.test_mower_1_my_lawn"
await setup_integration(hass, mock_config_entry) await setup_integration(hass, mock_config_entry)
values = mower_list_to_dictionary_dataclass( values = mower_list_to_dictionary_dataclass(
load_json_value_fixture("mower.json", DOMAIN) load_json_value_fixture("mower.json", DOMAIN),
mower_time_zone,
) )
values[TEST_MOWER_ID].work_areas[TEST_AREA_ID].enabled = boolean values[TEST_MOWER_ID].work_areas[TEST_AREA_ID].enabled = boolean
mock_automower_client.get_status.return_value = values mock_automower_client.get_status.return_value = values
@ -221,12 +224,9 @@ async def test_zones_deleted(
mock_automower_client: AsyncMock, mock_automower_client: AsyncMock,
mock_config_entry: MockConfigEntry, mock_config_entry: MockConfigEntry,
entity_registry: er.EntityRegistry, entity_registry: er.EntityRegistry,
values: dict[str, MowerAttributes],
) -> None: ) -> None:
"""Test if stay-out-zone is deleted after removed.""" """Test if stay-out-zone is deleted after removed."""
values = mower_list_to_dictionary_dataclass(
load_json_value_fixture("mower.json", DOMAIN)
)
await setup_integration(hass, mock_config_entry) await setup_integration(hass, mock_config_entry)
current_entries = len( current_entries = len(
er.async_entries_for_config_entry(entity_registry, mock_config_entry.entry_id) er.async_entries_for_config_entry(entity_registry, mock_config_entry.entry_id)