mirror of https://github.com/home-assistant/core
284 lines
8.2 KiB
Python
284 lines
8.2 KiB
Python
"""Tests for the melnor integration."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from collections.abc import Generator
|
|
from datetime import UTC, datetime, time, timedelta
|
|
from unittest.mock import AsyncMock, _patch, patch
|
|
|
|
from melnor_bluetooth.device import Device
|
|
import pytest
|
|
|
|
from homeassistant.components.bluetooth.models import BluetoothServiceInfoBleak
|
|
from homeassistant.components.melnor.const import DOMAIN
|
|
from homeassistant.const import CONF_ADDRESS
|
|
from homeassistant.core import HomeAssistant
|
|
|
|
from tests.common import MockConfigEntry
|
|
from tests.components.bluetooth import generate_advertisement_data, generate_ble_device
|
|
|
|
FAKE_ADDRESS_1 = "FAKE-ADDRESS-1"
|
|
FAKE_ADDRESS_2 = "FAKE-ADDRESS-2"
|
|
|
|
|
|
FAKE_SERVICE_INFO_1 = BluetoothServiceInfoBleak(
|
|
name="YM_TIMER%",
|
|
address=FAKE_ADDRESS_1,
|
|
rssi=-63,
|
|
manufacturer_data={
|
|
13: b"Y\x08\x02\x8f\x00\x00\x00\x00\x00\x00\xf0\x00\x00\xf0\x00\x00\xf0\x00\x00\xf0*\x9b\xcf\xbc"
|
|
},
|
|
service_uuids=[],
|
|
service_data={},
|
|
source="local",
|
|
device=generate_ble_device(FAKE_ADDRESS_1, None),
|
|
advertisement=generate_advertisement_data(local_name=""),
|
|
time=0,
|
|
connectable=True,
|
|
tx_power=-127,
|
|
)
|
|
|
|
FAKE_SERVICE_INFO_2 = BluetoothServiceInfoBleak(
|
|
name="YM_TIMER%",
|
|
address=FAKE_ADDRESS_2,
|
|
rssi=-63,
|
|
manufacturer_data={
|
|
13: b"Y\x08\x02\x8f\x00\x00\x00\x00\x00\x00\xf0\x00\x00\xf0\x00\x00\xf0\x00\x00\xf0*\x9b\xcf\xbc"
|
|
},
|
|
service_uuids=[],
|
|
service_data={},
|
|
source="local",
|
|
device=generate_ble_device(FAKE_ADDRESS_2, None),
|
|
advertisement=generate_advertisement_data(local_name=""),
|
|
time=0,
|
|
connectable=True,
|
|
tx_power=-127,
|
|
)
|
|
|
|
|
|
@pytest.fixture(autouse=True)
|
|
def mock_bluetooth(enable_bluetooth: None) -> None:
|
|
"""Auto mock bluetooth."""
|
|
|
|
|
|
class MockFrequency:
|
|
"""Mocked class for a Frequency."""
|
|
|
|
_duration: int
|
|
_interval: int
|
|
_is_watering: bool
|
|
_start_time: time
|
|
_next_run_time: datetime
|
|
|
|
def __init__(self) -> None:
|
|
"""Initialize a mocked frequency."""
|
|
self._duration = 0
|
|
self._interval = 0
|
|
self._is_watering = False
|
|
self._start_time = time(12, 0)
|
|
self._next_run_time = datetime(2021, 1, 1, 12, 0, tzinfo=UTC)
|
|
|
|
@property
|
|
def duration_minutes(self) -> int:
|
|
"""Return the duration in minutes."""
|
|
return self._duration
|
|
|
|
@duration_minutes.setter
|
|
def duration_minutes(self, duration: int) -> None:
|
|
"""Set the duration in minutes."""
|
|
self._duration = duration
|
|
|
|
@property
|
|
def interval_hours(self) -> int:
|
|
"""Return the interval in hours."""
|
|
return self._interval
|
|
|
|
@interval_hours.setter
|
|
def interval_hours(self, interval: int) -> None:
|
|
"""Set the interval in hours."""
|
|
self._interval = interval
|
|
|
|
@property
|
|
def start_time(self) -> time:
|
|
"""Return the start time."""
|
|
return self._start_time
|
|
|
|
@start_time.setter
|
|
def start_time(self, start_time: time) -> None:
|
|
"""Set the start time."""
|
|
self._start_time = start_time
|
|
|
|
@property
|
|
def is_watering(self) -> bool:
|
|
"""Return true if the frequency is currently watering."""
|
|
return self._is_watering
|
|
|
|
@property
|
|
def next_run_time(self) -> datetime:
|
|
"""Return the next run time."""
|
|
return self._next_run_time
|
|
|
|
@property
|
|
def schedule_end_time(self) -> datetime:
|
|
"""Return the schedule end time."""
|
|
return self._next_run_time + timedelta(minutes=self._duration)
|
|
|
|
|
|
class MockValve:
|
|
"""Mocked class for a Valve."""
|
|
|
|
_id: int
|
|
_is_watering: bool
|
|
_manual_watering_minutes: int
|
|
_end_time: int
|
|
_frequency: MockFrequency
|
|
_schedule_enabled: bool
|
|
|
|
def __init__(self, identifier: int) -> None:
|
|
"""Initialize a mocked valve."""
|
|
self._end_time = 0
|
|
self._id = identifier
|
|
self._is_watering = False
|
|
self._manual_watering_minutes = 0
|
|
self._schedule_enabled = False
|
|
|
|
self._frequency = MockFrequency()
|
|
|
|
@property
|
|
def id(self) -> int:
|
|
"""Return the valve id."""
|
|
return self._id
|
|
|
|
@property
|
|
def frequency(self):
|
|
"""Return the frequency."""
|
|
return self._frequency
|
|
|
|
@property
|
|
def is_watering(self):
|
|
"""Return true if the valve is currently watering."""
|
|
return self._is_watering
|
|
|
|
@property
|
|
def manual_watering_minutes(self):
|
|
"""Return the number of minutes the valve is set to manual watering."""
|
|
return self._manual_watering_minutes
|
|
|
|
@property
|
|
def next_cycle(self):
|
|
"""Return the end time of the current watering cycle."""
|
|
return self._frequency.next_run_time
|
|
|
|
@property
|
|
def schedule_enabled(self) -> bool:
|
|
"""Return true if the schedule is enabled."""
|
|
return self._schedule_enabled
|
|
|
|
@property
|
|
def watering_end_time(self) -> int:
|
|
"""Return the end time of the current watering cycle."""
|
|
return self._end_time
|
|
|
|
async def set_is_watering(self, is_watering: bool):
|
|
"""Set the valve to manual watering."""
|
|
self._is_watering = is_watering
|
|
|
|
async def set_manual_watering_minutes(self, minutes: int):
|
|
"""Set the valve to manual watering."""
|
|
self._manual_watering_minutes = minutes
|
|
|
|
async def set_frequency_interval_hours(self, interval: int):
|
|
"""Set the frequency interval in hours."""
|
|
self._frequency.interval_hours = interval
|
|
|
|
async def set_frequency_duration_minutes(self, duration: int):
|
|
"""Set the frequency duration in minutes."""
|
|
self._frequency.duration_minutes = duration
|
|
|
|
async def set_frequency_enabled(self, enabled: bool):
|
|
"""Set the frequency schedule enabled."""
|
|
self._schedule_enabled = enabled
|
|
|
|
async def set_frequency_start_time(self, value: time):
|
|
"""Set the frequency schedule enabled."""
|
|
self._frequency.start_time = value
|
|
|
|
|
|
def mock_config_entry(hass: HomeAssistant):
|
|
"""Return a mock config entry."""
|
|
|
|
entry = MockConfigEntry(
|
|
domain=DOMAIN,
|
|
unique_id=FAKE_ADDRESS_1,
|
|
data={CONF_ADDRESS: FAKE_ADDRESS_1},
|
|
)
|
|
entry.add_to_hass(hass)
|
|
|
|
return entry
|
|
|
|
|
|
def mock_melnor_device():
|
|
"""Return a mocked Melnor device."""
|
|
|
|
with patch("melnor_bluetooth.device.Device") as mock:
|
|
device = mock.return_value
|
|
|
|
device.connect = AsyncMock(return_value=True)
|
|
device.disconnect = AsyncMock(return_value=True)
|
|
device.fetch_state = AsyncMock(return_value=device)
|
|
device.push_state = AsyncMock(return_value=None)
|
|
|
|
device.battery_level = 80
|
|
device.mac = FAKE_ADDRESS_1
|
|
device.model = "test_model"
|
|
device.name = "test_melnor"
|
|
device.rssi = -50
|
|
|
|
device.zone1 = MockValve(0)
|
|
device.zone2 = MockValve(1)
|
|
device.zone3 = MockValve(2)
|
|
device.zone4 = MockValve(3)
|
|
|
|
device.__getitem__.side_effect = lambda key: getattr(device, key)
|
|
|
|
return device
|
|
|
|
|
|
@pytest.fixture
|
|
def mock_setup_entry() -> Generator[AsyncMock]:
|
|
"""Patch async setup entry to return True."""
|
|
with patch(
|
|
"homeassistant.components.melnor.async_setup_entry", return_value=True
|
|
) as mock_setup:
|
|
yield mock_setup
|
|
|
|
|
|
def patch_async_discovered_service_info(
|
|
return_value: list[BluetoothServiceInfoBleak],
|
|
) -> _patch:
|
|
"""Patch async_discovered_service_info a mocked device info."""
|
|
return patch(
|
|
"homeassistant.components.melnor.config_flow.async_discovered_service_info",
|
|
return_value=return_value,
|
|
)
|
|
|
|
|
|
def patch_async_ble_device_from_address(
|
|
return_value: BluetoothServiceInfoBleak | None = FAKE_SERVICE_INFO_1,
|
|
):
|
|
"""Patch async_ble_device_from_address to return a mocked BluetoothServiceInfoBleak."""
|
|
return patch(
|
|
"homeassistant.components.bluetooth.async_ble_device_from_address",
|
|
return_value=return_value,
|
|
)
|
|
|
|
|
|
def patch_melnor_device(device: Device = mock_melnor_device()):
|
|
"""Patch melnor_bluetooth.device to return a mocked Melnor device."""
|
|
return patch("homeassistant.components.melnor.Device", return_value=device)
|
|
|
|
|
|
def patch_async_register_callback():
|
|
"""Patch async_register_callback to return True."""
|
|
return patch("homeassistant.components.bluetooth.async_register_callback")
|