Add some basic tests for gardena (#96777)

This commit is contained in:
Joakim Plate 2023-07-17 21:13:13 +02:00 committed by GitHub
parent d80b7d0145
commit d02bf837a6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 426 additions and 2 deletions

View File

@ -1,7 +1,18 @@
"""Tests for the Gardena Bluetooth integration.""" """Tests for the Gardena Bluetooth integration."""
from unittest.mock import patch
from homeassistant.components.gardena_bluetooth.const import DOMAIN
from homeassistant.components.gardena_bluetooth.coordinator import Coordinator
from homeassistant.const import Platform
from homeassistant.core import HomeAssistant
from homeassistant.helpers.service_info.bluetooth import BluetoothServiceInfo from homeassistant.helpers.service_info.bluetooth import BluetoothServiceInfo
from tests.common import MockConfigEntry
from tests.components.bluetooth import (
inject_bluetooth_service_info,
)
WATER_TIMER_SERVICE_INFO = BluetoothServiceInfo( WATER_TIMER_SERVICE_INFO = BluetoothServiceInfo(
name="Timer", name="Timer",
address="00000000-0000-0000-0000-000000000001", address="00000000-0000-0000-0000-000000000001",
@ -59,3 +70,18 @@ UNSUPPORTED_GROUP_SERVICE_INFO = BluetoothServiceInfo(
service_uuids=["98bd0001-0b0e-421a-84e5-ddbf75dc6de4"], service_uuids=["98bd0001-0b0e-421a-84e5-ddbf75dc6de4"],
source="local", source="local",
) )
async def setup_entry(
hass: HomeAssistant, mock_entry: MockConfigEntry, platforms: list[Platform]
) -> Coordinator:
"""Make sure the device is available."""
inject_bluetooth_service_info(hass, WATER_TIMER_SERVICE_INFO)
with patch("homeassistant.components.gardena_bluetooth.PLATFORMS", platforms):
mock_entry.add_to_hass(hass)
await hass.config_entries.async_setup(mock_entry.entry_id)
await hass.async_block_till_done()
return hass.data[DOMAIN][mock_entry.entry_id]

View File

@ -1,10 +1,30 @@
"""Common fixtures for the Gardena Bluetooth tests.""" """Common fixtures for the Gardena Bluetooth tests."""
from collections.abc import Generator from collections.abc import Generator
from typing import Any
from unittest.mock import AsyncMock, Mock, patch from unittest.mock import AsyncMock, Mock, patch
from freezegun import freeze_time
from gardena_bluetooth.client import Client from gardena_bluetooth.client import Client
from gardena_bluetooth.const import DeviceInformation
from gardena_bluetooth.exceptions import CharacteristicNotFound
from gardena_bluetooth.parse import Characteristic
import pytest import pytest
from homeassistant.components.gardena_bluetooth.const import DOMAIN
from homeassistant.const import CONF_ADDRESS
from . import WATER_TIMER_SERVICE_INFO
from tests.common import MockConfigEntry
@pytest.fixture
def mock_entry():
"""Create hass config fixture."""
return MockConfigEntry(
domain=DOMAIN, data={CONF_ADDRESS: WATER_TIMER_SERVICE_INFO.address}
)
@pytest.fixture @pytest.fixture
def mock_setup_entry() -> Generator[AsyncMock, None, None]: def mock_setup_entry() -> Generator[AsyncMock, None, None]:
@ -16,15 +36,52 @@ def mock_setup_entry() -> Generator[AsyncMock, None, None]:
yield mock_setup_entry yield mock_setup_entry
@pytest.fixture
def mock_read_char_raw():
"""Mock data on device."""
return {
DeviceInformation.firmware_version.uuid: b"1.2.3",
DeviceInformation.model_number.uuid: b"Mock Model",
}
@pytest.fixture(autouse=True) @pytest.fixture(autouse=True)
def mock_client(enable_bluetooth): def mock_client(enable_bluetooth: None, mock_read_char_raw: dict[str, Any]) -> None:
"""Auto mock bluetooth.""" """Auto mock bluetooth."""
client = Mock(spec_set=Client) client = Mock(spec_set=Client)
client.get_all_characteristics_uuid.return_value = set()
SENTINEL = object()
def _read_char(char: Characteristic, default: Any = SENTINEL):
try:
return char.decode(mock_read_char_raw[char.uuid])
except KeyError:
if default is SENTINEL:
raise CharacteristicNotFound from KeyError
return default
def _read_char_raw(uuid: str, default: Any = SENTINEL):
try:
return mock_read_char_raw[uuid]
except KeyError:
if default is SENTINEL:
raise CharacteristicNotFound from KeyError
return default
def _all_char():
return set(mock_read_char_raw.keys())
client.read_char.side_effect = _read_char
client.read_char_raw.side_effect = _read_char_raw
client.get_all_characteristics_uuid.side_effect = _all_char
with patch( with patch(
"homeassistant.components.gardena_bluetooth.config_flow.Client", "homeassistant.components.gardena_bluetooth.config_flow.Client",
return_value=client, return_value=client,
), patch(
"homeassistant.components.gardena_bluetooth.Client", return_value=client
), freeze_time(
"2023-01-01", tz_offset=1
): ):
yield client yield client

View File

@ -0,0 +1,28 @@
# serializer version: 1
# name: test_setup
DeviceRegistryEntrySnapshot({
'area_id': None,
'config_entries': <ANY>,
'configuration_url': None,
'connections': set({
}),
'disabled_by': None,
'entry_type': None,
'hw_version': None,
'id': <ANY>,
'identifiers': set({
tuple(
'gardena_bluetooth',
'00000000-0000-0000-0000-000000000001',
),
}),
'is_new': False,
'manufacturer': None,
'model': 'Mock Model',
'name': 'Mock Title',
'name_by_user': None,
'suggested_area': None,
'sw_version': '1.2.3',
'via_device_id': None,
})
# ---

View File

@ -0,0 +1,86 @@
# serializer version: 1
# name: test_setup[98bd0f13-0b0e-421a-84e5-ddbf75dc6de4-raw1-number.mock_title_remaining_open_time]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Mock Title Remaining open time',
'max': 86400,
'min': 0.0,
'mode': <NumberMode.AUTO: 'auto'>,
'step': 60.0,
'unit_of_measurement': <UnitOfTime.SECONDS: 's'>,
}),
'context': <ANY>,
'entity_id': 'number.mock_title_remaining_open_time',
'last_changed': <ANY>,
'last_updated': <ANY>,
'state': '100.0',
})
# ---
# name: test_setup[98bd0f13-0b0e-421a-84e5-ddbf75dc6de4-raw1-number.mock_title_remaining_open_time].1
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Mock Title Remaining open time',
'max': 86400,
'min': 0.0,
'mode': <NumberMode.AUTO: 'auto'>,
'step': 60.0,
'unit_of_measurement': <UnitOfTime.SECONDS: 's'>,
}),
'context': <ANY>,
'entity_id': 'number.mock_title_remaining_open_time',
'last_changed': <ANY>,
'last_updated': <ANY>,
'state': '10.0',
})
# ---
# name: test_setup[98bd0f13-0b0e-421a-84e5-ddbf75dc6de4-raw2-number.mock_title_open_for]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Mock Title Open for',
'max': 1440,
'min': 0.0,
'mode': <NumberMode.BOX: 'box'>,
'step': 1.0,
'unit_of_measurement': 'min',
}),
'context': <ANY>,
'entity_id': 'number.mock_title_open_for',
'last_changed': <ANY>,
'last_updated': <ANY>,
'state': 'unknown',
})
# ---
# name: test_setup[98bd0f14-0b0e-421a-84e5-ddbf75dc6de4-raw0-number.mock_title_manual_watering_time]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Mock Title Manual watering time',
'max': 86400,
'min': 0.0,
'mode': <NumberMode.BOX: 'box'>,
'step': 60,
'unit_of_measurement': <UnitOfTime.SECONDS: 's'>,
}),
'context': <ANY>,
'entity_id': 'number.mock_title_manual_watering_time',
'last_changed': <ANY>,
'last_updated': <ANY>,
'state': '100.0',
})
# ---
# name: test_setup[98bd0f14-0b0e-421a-84e5-ddbf75dc6de4-raw0-number.mock_title_manual_watering_time].1
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Mock Title Manual watering time',
'max': 86400,
'min': 0.0,
'mode': <NumberMode.BOX: 'box'>,
'step': 60,
'unit_of_measurement': <UnitOfTime.SECONDS: 's'>,
}),
'context': <ANY>,
'entity_id': 'number.mock_title_manual_watering_time',
'last_changed': <ANY>,
'last_updated': <ANY>,
'state': '10.0',
})
# ---

View File

@ -0,0 +1,57 @@
# serializer version: 1
# name: test_setup[98bd0f13-0b0e-421a-84e5-ddbf75dc6de4-raw1-sensor.mock_title_valve_closing]
StateSnapshot({
'attributes': ReadOnlyDict({
'device_class': 'timestamp',
'friendly_name': 'Mock Title Valve closing',
}),
'context': <ANY>,
'entity_id': 'sensor.mock_title_valve_closing',
'last_changed': <ANY>,
'last_updated': <ANY>,
'state': '2023-01-01T01:01:40+00:00',
})
# ---
# name: test_setup[98bd0f13-0b0e-421a-84e5-ddbf75dc6de4-raw1-sensor.mock_title_valve_closing].1
StateSnapshot({
'attributes': ReadOnlyDict({
'device_class': 'timestamp',
'friendly_name': 'Mock Title Valve closing',
}),
'context': <ANY>,
'entity_id': 'sensor.mock_title_valve_closing',
'last_changed': <ANY>,
'last_updated': <ANY>,
'state': '2023-01-01T01:00:10+00:00',
})
# ---
# name: test_setup[98bd2a19-0b0e-421a-84e5-ddbf75dc6de4-raw0-sensor.mock_title_battery]
StateSnapshot({
'attributes': ReadOnlyDict({
'device_class': 'battery',
'friendly_name': 'Mock Title Battery',
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
'unit_of_measurement': '%',
}),
'context': <ANY>,
'entity_id': 'sensor.mock_title_battery',
'last_changed': <ANY>,
'last_updated': <ANY>,
'state': '100',
})
# ---
# name: test_setup[98bd2a19-0b0e-421a-84e5-ddbf75dc6de4-raw0-sensor.mock_title_battery].1
StateSnapshot({
'attributes': ReadOnlyDict({
'device_class': 'battery',
'friendly_name': 'Mock Title Battery',
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
'unit_of_measurement': '%',
}),
'context': <ANY>,
'entity_id': 'sensor.mock_title_battery',
'last_changed': <ANY>,
'last_updated': <ANY>,
'state': '10',
})
# ---

View File

@ -0,0 +1,58 @@
"""Test the Gardena Bluetooth setup."""
from datetime import timedelta
from unittest.mock import Mock
from syrupy.assertion import SnapshotAssertion
from homeassistant.components.gardena_bluetooth import DeviceUnavailable
from homeassistant.components.gardena_bluetooth.const import DOMAIN
from homeassistant.config_entries import ConfigEntryState
from homeassistant.core import HomeAssistant
from homeassistant.helpers import device_registry as dr
from homeassistant.util import utcnow
from . import WATER_TIMER_SERVICE_INFO
from tests.common import MockConfigEntry, async_fire_time_changed
async def test_setup(
hass: HomeAssistant,
mock_entry: MockConfigEntry,
snapshot: SnapshotAssertion,
) -> None:
"""Test setup creates expected devices."""
mock_entry.add_to_hass(hass)
await hass.config_entries.async_setup(mock_entry.entry_id)
await hass.async_block_till_done()
assert mock_entry.state is ConfigEntryState.LOADED
device_registry = dr.async_get(hass)
device = device_registry.async_get_device(
identifiers={(DOMAIN, WATER_TIMER_SERVICE_INFO.address)}
)
assert device == snapshot
async def test_setup_retry(
hass: HomeAssistant, mock_entry: MockConfigEntry, mock_client: Mock
) -> None:
"""Test setup creates expected devices."""
original_read_char = mock_client.read_char.side_effect
mock_client.read_char.side_effect = DeviceUnavailable
mock_entry.add_to_hass(hass)
await hass.config_entries.async_setup(mock_entry.entry_id)
await hass.async_block_till_done()
assert mock_entry.state is ConfigEntryState.SETUP_RETRY
mock_client.read_char.side_effect = original_read_char
async_fire_time_changed(hass, utcnow() + timedelta(seconds=10))
await hass.async_block_till_done()
assert mock_entry.state is ConfigEntryState.LOADED

View File

@ -0,0 +1,60 @@
"""Test Gardena Bluetooth sensor."""
from gardena_bluetooth.const import Valve
import pytest
from syrupy.assertion import SnapshotAssertion
from homeassistant.const import Platform
from homeassistant.core import HomeAssistant
from . import setup_entry
from tests.common import MockConfigEntry
@pytest.mark.parametrize(
("uuid", "raw", "entity_id"),
[
(
Valve.manual_watering_time.uuid,
[
Valve.manual_watering_time.encode(100),
Valve.manual_watering_time.encode(10),
],
"number.mock_title_manual_watering_time",
),
(
Valve.remaining_open_time.uuid,
[
Valve.remaining_open_time.encode(100),
Valve.remaining_open_time.encode(10),
],
"number.mock_title_remaining_open_time",
),
(
Valve.remaining_open_time.uuid,
[Valve.remaining_open_time.encode(100)],
"number.mock_title_open_for",
),
],
)
async def test_setup(
hass: HomeAssistant,
snapshot: SnapshotAssertion,
mock_entry: MockConfigEntry,
mock_read_char_raw: dict[str, bytes],
uuid: str,
raw: list[bytes],
entity_id: str,
) -> None:
"""Test setup creates expected entities."""
mock_read_char_raw[uuid] = raw[0]
coordinator = await setup_entry(hass, mock_entry, [Platform.NUMBER])
assert hass.states.get(entity_id) == snapshot
for char_raw in raw[1:]:
mock_read_char_raw[uuid] = char_raw
await coordinator.async_refresh()
assert hass.states.get(entity_id) == snapshot

View File

@ -0,0 +1,52 @@
"""Test Gardena Bluetooth sensor."""
from gardena_bluetooth.const import Battery, Valve
import pytest
from syrupy.assertion import SnapshotAssertion
from homeassistant.const import Platform
from homeassistant.core import HomeAssistant
from . import setup_entry
from tests.common import MockConfigEntry
@pytest.mark.parametrize(
("uuid", "raw", "entity_id"),
[
(
Battery.battery_level.uuid,
[Battery.battery_level.encode(100), Battery.battery_level.encode(10)],
"sensor.mock_title_battery",
),
(
Valve.remaining_open_time.uuid,
[
Valve.remaining_open_time.encode(100),
Valve.remaining_open_time.encode(10),
],
"sensor.mock_title_valve_closing",
),
],
)
async def test_setup(
hass: HomeAssistant,
snapshot: SnapshotAssertion,
mock_entry: MockConfigEntry,
mock_read_char_raw: dict[str, bytes],
uuid: str,
raw: list[bytes],
entity_id: str,
) -> None:
"""Test setup creates expected entities."""
mock_read_char_raw[uuid] = raw[0]
coordinator = await setup_entry(hass, mock_entry, [Platform.SENSOR])
assert hass.states.get(entity_id) == snapshot
for char_raw in raw[1:]:
mock_read_char_raw[uuid] = char_raw
await coordinator.async_refresh()
assert hass.states.get(entity_id) == snapshot