Bump aioautomower to 2024.10.3 ()

pull/129110/head
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,
device_registry as dr,
)
from homeassistant.util import dt as dt_util
from . import api
from .const import DOMAIN
@ -48,7 +49,11 @@ async def async_setup_entry(hass: HomeAssistant, entry: AutomowerConfigEntry) ->
aiohttp_client.async_get_clientsession(hass),
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:
await api_api.async_get_access_token()
except ClientResponseError as err:

View File

@ -11,7 +11,6 @@ from aioautomower.session import AutomowerSession
from homeassistant.components.button import ButtonEntity, ButtonEntityDescription
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.util import dt as dt_util
from . import AutomowerConfigEntry
from .coordinator import AutomowerDataUpdateCoordinator
@ -24,19 +23,6 @@ from .entity import (
_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)
class AutomowerButtonEntityDescription(ButtonEntityDescription):
"""Describes Automower button entities."""
@ -58,7 +44,7 @@ MOWER_BUTTON_TYPES: tuple[AutomowerButtonEntityDescription, ...] = (
key="sync_clock",
translation_key="sync_clock",
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(
summary=make_name_string(work_area_name, program_event.schedule_no),
start=program_event.start.replace(tzinfo=dt_util.DEFAULT_TIME_ZONE),
end=program_event.end.replace(tzinfo=dt_util.DEFAULT_TIME_ZONE),
start=program_event.start,
end=program_event.end,
rrule=program_event.rrule_str,
)

View File

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

View File

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

View File

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

View File

@ -4,6 +4,7 @@ from collections.abc import Generator
import time
from unittest.mock import AsyncMock, patch
from aioautomower.model import MowerAttributes
from aioautomower.session import AutomowerSession, _MowerCommands
from aioautomower.utils import mower_list_to_dictionary_dataclass
from aiohttp import ClientWebSocketResponse
@ -16,6 +17,7 @@ from homeassistant.components.application_credentials import (
from homeassistant.components.husqvarna_automower.const import DOMAIN
from homeassistant.core import HomeAssistant
from homeassistant.setup import async_setup_component
from homeassistant.util import dt as dt_util
from .const import CLIENT_ID, CLIENT_SECRET, USER_ID
@ -40,6 +42,21 @@ def mock_scope() -> str:
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
def mock_config_entry(jwt: str, expires_at: int, scope: str) -> MockConfigEntry:
"""Return the default mocked config entry."""
@ -81,17 +98,13 @@ async def setup_credentials(hass: HomeAssistant) -> None:
@pytest.fixture
def mock_automower_client() -> Generator[AsyncMock]:
def mock_automower_client(values) -> Generator[AsyncMock]:
"""Mock a Husqvarna Automower client."""
mower_dict = mower_list_to_dictionary_dataclass(
load_json_value_fixture("mower.json", DOMAIN)
)
mock = AsyncMock(spec=AutomowerSession)
mock.auth = AsyncMock(side_effect=ClientWebSocketResponse)
mock.commands = AsyncMock(spec_set=_MowerCommands)
mock.get_status.return_value = mower_dict
mock.get_status.return_value = values
with patch(
"homeassistant.components.husqvarna_automower.AutomowerSession",

View File

@ -68,6 +68,11 @@
'start': '2023-06-10T01:00:00+02:00',
'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({

View File

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

View File

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

View File

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

View File

@ -2,16 +2,14 @@
import datetime
from unittest.mock import AsyncMock, patch
import zoneinfo
from aioautomower.exceptions import ApiException
from aioautomower.utils import mower_list_to_dictionary_dataclass
from aioautomower.model import MowerAttributes
from freezegun.api import FrozenDateTimeFactory
import pytest
from syrupy import SnapshotAssertion
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.const import (
ATTR_ENTITY_ID,
@ -26,12 +24,7 @@ from homeassistant.helpers import entity_registry as er
from . import setup_integration
from .const import TEST_MOWER_ID
from tests.common import (
MockConfigEntry,
async_fire_time_changed,
load_json_value_fixture,
snapshot_platform,
)
from tests.common import MockConfigEntry, async_fire_time_changed, snapshot_platform
@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_config_entry: MockConfigEntry,
freezer: FrozenDateTimeFactory,
values: dict[str, MowerAttributes],
) -> None:
"""Test error confirm button command."""
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.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
mock_automower_client.get_status.return_value = values
freezer.tick(SCAN_INTERVAL)
@ -99,6 +90,7 @@ async def test_sync_clock(
mock_automower_client: AsyncMock,
mock_config_entry: MockConfigEntry,
freezer: FrozenDateTimeFactory,
values: dict[str, MowerAttributes],
) -> None:
"""Test sync clock button command."""
entity_id = "button.test_mower_1_sync_clock"
@ -106,9 +98,6 @@ async def test_sync_clock(
state = hass.states.get(entity_id)
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
await hass.services.async_call(
@ -118,12 +107,7 @@ async def test_sync_clock(
blocking=True,
)
mocked_method = mock_automower_client.commands.set_datetime
# datetime(2024, 2, 29, 11, tzinfo=datetime.UTC) is in local time of the tests
# 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")),
)
mocked_method.assert_called_once_with(TEST_MOWER_ID)
await hass.async_block_till_done()
state = hass.states.get(entity_id)
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 unittest.mock import AsyncMock
import urllib
import zoneinfo
from aioautomower.utils import mower_list_to_dictionary_dataclass
from freezegun.api import FrozenDateTimeFactory
@ -93,12 +94,16 @@ async def test_empty_calendar(
mock_config_entry: MockConfigEntry,
freezer: FrozenDateTimeFactory,
get_events: GetEventsFn,
mower_time_zone: zoneinfo.ZoneInfo,
) -> None:
"""State if there is no schedule set."""
await setup_integration(hass, mock_config_entry)
json_values = load_json_value_fixture("mower.json", DOMAIN)
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
freezer.tick(SCAN_INTERVAL)
async_fire_time_changed(hass)

View File

@ -2,6 +2,7 @@
import datetime
from unittest.mock import AsyncMock
import zoneinfo
import pytest
from syrupy.assertion import SnapshotAssertion
@ -21,7 +22,9 @@ from tests.components.diagnostics import (
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(
hass: HomeAssistant,
hass_client: ClientSessionGenerator,
@ -40,7 +43,9 @@ async def test_entry_diagnostics(
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(
hass: HomeAssistant,
hass_client: ClientSessionGenerator,

View File

@ -10,7 +10,7 @@ from aioautomower.exceptions import (
AuthException,
HusqvarnaWSServerHandshakeError,
)
from aioautomower.utils import mower_list_to_dictionary_dataclass
from aioautomower.model import MowerAttributes
from freezegun.api import FrozenDateTimeFactory
import pytest
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 .const import TEST_MOWER_ID
from tests.common import (
MockConfigEntry,
async_fire_time_changed,
load_json_value_fixture,
)
from tests.common import MockConfigEntry, async_fire_time_changed
from tests.test_util.aiohttp import AiohttpClientMocker
@ -172,12 +168,10 @@ async def test_workarea_deleted(
mock_automower_client: AsyncMock,
mock_config_entry: MockConfigEntry,
entity_registry: er.EntityRegistry,
values: dict[str, MowerAttributes],
) -> None:
"""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)
current_entries = len(
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,
device_registry: dr.DeviceRegistry,
entity_registry: er.EntityRegistry,
values: dict[str, MowerAttributes],
) -> None:
"""Test automatic registry cleanup."""
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)
)
values = mower_list_to_dictionary_dataclass(
load_json_value_fixture("mower.json", DOMAIN)
)
values.pop(TEST_MOWER_ID)
mock_automower_client.get_status.return_value = values
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 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
import pytest
from voluptuous.error import MultipleInvalid
@ -18,11 +18,7 @@ from homeassistant.exceptions import HomeAssistantError, ServiceValidationError
from . import setup_integration
from .const import TEST_MOWER_ID
from tests.common import (
MockConfigEntry,
async_fire_time_changed,
load_json_value_fixture,
)
from tests.common import MockConfigEntry, async_fire_time_changed
async def test_lawn_mower_states(
@ -30,21 +26,23 @@ async def test_lawn_mower_states(
mock_automower_client: AsyncMock,
mock_config_entry: MockConfigEntry,
freezer: FrozenDateTimeFactory,
values: dict[str, MowerAttributes],
) -> None:
"""Test lawn_mower state."""
values = mower_list_to_dictionary_dataclass(
load_json_value_fixture("mower.json", DOMAIN)
)
await setup_integration(hass, mock_config_entry)
state = hass.states.get("lawn_mower.test_mower_1")
assert state is not None
assert state.state == LawnMowerActivity.DOCKED
for activity, state, expected_state in (
("UNKNOWN", "PAUSED", LawnMowerActivity.PAUSED),
("MOWING", "NOT_APPLICABLE", LawnMowerActivity.MOWING),
("NOT_APPLICABLE", "ERROR", LawnMowerActivity.ERROR),
("GOING_HOME", "IN_OPERATION", LawnMowerActivity.RETURNING),
(MowerActivities.UNKNOWN, MowerStates.PAUSED, LawnMowerActivity.PAUSED),
(MowerActivities.MOWING, MowerStates.NOT_APPLICABLE, LawnMowerActivity.MOWING),
(MowerActivities.NOT_APPLICABLE, MowerStates.ERROR, LawnMowerActivity.ERROR),
(
MowerActivities.GOING_HOME,
MowerStates.IN_OPERATION,
LawnMowerActivity.RETURNING,
),
):
values[TEST_MOWER_ID].mower.activity = activity
values[TEST_MOWER_ID].mower.state = state
@ -253,12 +251,10 @@ async def test_lawn_mower_wrong_service_commands(
mock_automower_client: AsyncMock,
mock_config_entry: MockConfigEntry,
freezer: FrozenDateTimeFactory,
values: dict[str, MowerAttributes],
) -> None:
"""Test lawn_mower commands."""
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
mock_automower_client.get_status.return_value = values
freezer.tick(SCAN_INTERVAL)

View File

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

View File

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

View File

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

View File

@ -2,9 +2,10 @@
from datetime import timedelta
from unittest.mock import AsyncMock, patch
import zoneinfo
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 freezegun.api import FrozenDateTimeFactory
import pytest
@ -46,11 +47,9 @@ async def test_switch_states(
mock_automower_client: AsyncMock,
mock_config_entry: MockConfigEntry,
freezer: FrozenDateTimeFactory,
values: dict[str, MowerAttributes],
) -> None:
"""Test switch state."""
values = mower_list_to_dictionary_dataclass(
load_json_value_fixture("mower.json", DOMAIN)
)
await setup_integration(hass, mock_config_entry)
for mode, expected_state in (
@ -122,12 +121,14 @@ async def test_stay_out_zone_switch_commands(
mock_automower_client: AsyncMock,
mock_config_entry: MockConfigEntry,
freezer: FrozenDateTimeFactory,
mower_time_zone: zoneinfo.ZoneInfo,
) -> None:
"""Test switch commands."""
entity_id = "switch.test_mower_1_avoid_danger_zone"
await setup_integration(hass, mock_config_entry)
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
mock_automower_client.get_status.return_value = values
@ -177,12 +178,14 @@ async def test_work_area_switch_commands(
mock_automower_client: AsyncMock,
mock_config_entry: MockConfigEntry,
freezer: FrozenDateTimeFactory,
mower_time_zone: zoneinfo.ZoneInfo,
) -> None:
"""Test switch commands."""
entity_id = "switch.test_mower_1_my_lawn"
await setup_integration(hass, mock_config_entry)
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
mock_automower_client.get_status.return_value = values
@ -221,12 +224,9 @@ async def test_zones_deleted(
mock_automower_client: AsyncMock,
mock_config_entry: MockConfigEntry,
entity_registry: er.EntityRegistry,
values: dict[str, MowerAttributes],
) -> None:
"""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)
current_entries = len(
er.async_entries_for_config_entry(entity_registry, mock_config_entry.entry_id)