AQM tests in Alexa Devices (#182678)

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
Co-authored-by: Simone Chemelli <simone.chemelli@gmail.com>
This commit is contained in:
jameson_uk
2026-09-29 17:42:08 +02:00
committed by GitHub
co-authored by Claude Sonnet 5 Simone Chemelli
parent 7d1100f5d4
commit 31ff8d6183
5 changed files with 466 additions and 13 deletions
+78
View File
@@ -2,6 +2,7 @@
from datetime import UTC, datetime
from aioamazondevices.const.devices import DEVICE_TYPE_AQM
from aioamazondevices.const.schedules import (
NOTIFICATION_ALARM,
NOTIFICATION_REMINDER,
@@ -121,6 +122,83 @@ TEST_DEVICE_2 = AmazonDevice(
voice_control_supported=True,
)
TEST_DEVICE_AQM_SN = "aqm_test_serial_number"
TEST_DEVICE_AQM = AmazonDevice(
account_name="Air Quality Monitor Test",
# Air Quality Monitors are discovered via a separate GraphQL endpoint and
# never expose voice, media or notification capabilities.
capabilities=[],
device_family="AIR_QUALITY_MONITOR",
device_type=DEVICE_TYPE_AQM,
household_device=False,
device_owner_customer_id="amazon_ower_id",
device_cluster_members={TEST_DEVICE_AQM_SN: DEVICE_TYPE_AQM},
parent_clusters=[],
online=True,
serial_number=TEST_DEVICE_AQM_SN,
manufacturer="Amazon",
model="Air Quality Monitor",
hardware_version=None,
software_version="aqm_test_software_version",
entity_id="66666666-7777-8888-9999-000000000000",
endpoint_id="G1234567890123456789012345678C",
sensors={
"Humidity": AmazonDeviceSensor(
name="Humidity",
value="45",
error=False,
error_msg=None,
error_type=None,
scale="%",
),
"VOC": AmazonDeviceSensor(
name="VOC",
value="120",
error=False,
error_msg=None,
error_type=None,
scale=None,
),
"PM25": AmazonDeviceSensor(
name="PM25",
value="8",
error=False,
error_msg=None,
error_type=None,
scale="MicroGramsPerCubicMeter",
),
"PM10": AmazonDeviceSensor(
name="PM10",
value="12",
error=False,
error_msg=None,
error_type=None,
scale="MicroGramsPerCubicMeter",
),
"CO": AmazonDeviceSensor(
name="CO",
value="0.5",
error=False,
error_msg=None,
error_type=None,
scale="ppm",
),
"Air Quality": AmazonDeviceSensor(
name="Air Quality",
value="42",
error=False,
error_msg=None,
error_type=None,
scale=None,
),
},
notifications_supported=False,
notifications={},
media_player_supported=False,
communication_settings={},
voice_control_supported=False,
)
TEST_VOCAL_RECORD_INITIAL = AmazonVocalRecord(
timestamp=1000,
history_type="WAKE_WORD_UTTERANCE",
@@ -1,5 +1,332 @@
# serializer version: 1
# name: test_all_entities[sensor.echo_test_next_alarm-entry]
# name: test_all_entities[aqm][sensor.air_quality_monitor_test_air_quality_index-entry]
EntityRegistryEntrySnapshot({
'aliases': list([
None,
]),
'area_id': None,
'capabilities': dict({
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
}),
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'sensor',
'entity_category': None,
'entity_id': 'sensor.air_quality_monitor_test_air_quality_index',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'object_id_base': 'Air quality index',
'options': dict({
}),
'original_device_class': <SensorDeviceClass.AQI: 'aqi'>,
'original_icon': None,
'original_name': 'Air quality index',
'platform': 'alexa_devices',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': None,
'unique_id': 'aqm_test_serial_number-Air Quality',
'unit_of_measurement': None,
})
# ---
# name: test_all_entities[aqm][sensor.air_quality_monitor_test_air_quality_index-state]
StateSnapshot({
'attributes': ReadOnlyDict({
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'aqi',
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Air Quality Monitor Test Air quality index',
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
}),
'context': <ANY>,
'entity_id': 'sensor.air_quality_monitor_test_air_quality_index',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': '42',
})
# ---
# name: test_all_entities[aqm][sensor.air_quality_monitor_test_carbon_monoxide-entry]
EntityRegistryEntrySnapshot({
'aliases': list([
None,
]),
'area_id': None,
'capabilities': dict({
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
}),
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'sensor',
'entity_category': None,
'entity_id': 'sensor.air_quality_monitor_test_carbon_monoxide',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'object_id_base': 'Carbon monoxide',
'options': dict({
}),
'original_device_class': <SensorDeviceClass.CO: 'carbon_monoxide'>,
'original_icon': None,
'original_name': 'Carbon monoxide',
'platform': 'alexa_devices',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': None,
'unique_id': 'aqm_test_serial_number-CO',
'unit_of_measurement': <UnitOfRatio.PARTS_PER_MILLION: 'ppm'>,
})
# ---
# name: test_all_entities[aqm][sensor.air_quality_monitor_test_carbon_monoxide-state]
StateSnapshot({
'attributes': ReadOnlyDict({
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'carbon_monoxide',
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Air Quality Monitor Test Carbon monoxide',
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PARTS_PER_MILLION: 'ppm'>,
}),
'context': <ANY>,
'entity_id': 'sensor.air_quality_monitor_test_carbon_monoxide',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': '0.5',
})
# ---
# name: test_all_entities[aqm][sensor.air_quality_monitor_test_humidity-entry]
EntityRegistryEntrySnapshot({
'aliases': list([
None,
]),
'area_id': None,
'capabilities': dict({
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
}),
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'sensor',
'entity_category': None,
'entity_id': 'sensor.air_quality_monitor_test_humidity',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'object_id_base': 'Humidity',
'options': dict({
}),
'original_device_class': <SensorDeviceClass.HUMIDITY: 'humidity'>,
'original_icon': None,
'original_name': 'Humidity',
'platform': 'alexa_devices',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': None,
'unique_id': 'aqm_test_serial_number-Humidity',
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
})
# ---
# name: test_all_entities[aqm][sensor.air_quality_monitor_test_humidity-state]
StateSnapshot({
'attributes': ReadOnlyDict({
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'humidity',
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Air Quality Monitor Test Humidity',
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
}),
'context': <ANY>,
'entity_id': 'sensor.air_quality_monitor_test_humidity',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': '45',
})
# ---
# name: test_all_entities[aqm][sensor.air_quality_monitor_test_pm10-entry]
EntityRegistryEntrySnapshot({
'aliases': list([
None,
]),
'area_id': None,
'capabilities': dict({
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
}),
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'sensor',
'entity_category': None,
'entity_id': 'sensor.air_quality_monitor_test_pm10',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'object_id_base': 'PM10',
'options': dict({
}),
'original_device_class': <SensorDeviceClass.PM10: 'pm10'>,
'original_icon': None,
'original_name': 'PM10',
'platform': 'alexa_devices',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': None,
'unique_id': 'aqm_test_serial_number-PM10',
'unit_of_measurement': <UnitOfDensity.MICROGRAMS_PER_CUBIC_METER: 'μg/m³'>,
})
# ---
# name: test_all_entities[aqm][sensor.air_quality_monitor_test_pm10-state]
StateSnapshot({
'attributes': ReadOnlyDict({
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'pm10',
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Air Quality Monitor Test PM10',
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfDensity.MICROGRAMS_PER_CUBIC_METER: 'μg/m³'>,
}),
'context': <ANY>,
'entity_id': 'sensor.air_quality_monitor_test_pm10',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': '12',
})
# ---
# name: test_all_entities[aqm][sensor.air_quality_monitor_test_pm2_5-entry]
EntityRegistryEntrySnapshot({
'aliases': list([
None,
]),
'area_id': None,
'capabilities': dict({
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
}),
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'sensor',
'entity_category': None,
'entity_id': 'sensor.air_quality_monitor_test_pm2_5',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'object_id_base': 'PM2.5',
'options': dict({
}),
'original_device_class': <SensorDeviceClass.PM25: 'pm25'>,
'original_icon': None,
'original_name': 'PM2.5',
'platform': 'alexa_devices',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': None,
'unique_id': 'aqm_test_serial_number-PM25',
'unit_of_measurement': <UnitOfDensity.MICROGRAMS_PER_CUBIC_METER: 'μg/m³'>,
})
# ---
# name: test_all_entities[aqm][sensor.air_quality_monitor_test_pm2_5-state]
StateSnapshot({
'attributes': ReadOnlyDict({
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'pm25',
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Air Quality Monitor Test PM2.5',
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfDensity.MICROGRAMS_PER_CUBIC_METER: 'μg/m³'>,
}),
'context': <ANY>,
'entity_id': 'sensor.air_quality_monitor_test_pm2_5',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': '8',
})
# ---
# name: test_all_entities[aqm][sensor.air_quality_monitor_test_volatile_organic_compounds_index-entry]
EntityRegistryEntrySnapshot({
'aliases': list([
None,
]),
'area_id': None,
'capabilities': dict({
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
}),
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'sensor',
'entity_category': None,
'entity_id': 'sensor.air_quality_monitor_test_volatile_organic_compounds_index',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'object_id_base': 'Volatile organic compounds index',
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'Volatile organic compounds index',
'platform': 'alexa_devices',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'voc_index',
'unique_id': 'aqm_test_serial_number-VOC',
'unit_of_measurement': None,
})
# ---
# name: test_all_entities[aqm][sensor.air_quality_monitor_test_volatile_organic_compounds_index-state]
StateSnapshot({
'attributes': ReadOnlyDict({
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Air Quality Monitor Test Volatile organic compounds index',
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
}),
'context': <ANY>,
'entity_id': 'sensor.air_quality_monitor_test_volatile_organic_compounds_index',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': '120',
})
# ---
# name: test_all_entities[echo][sensor.echo_test_next_alarm-entry]
EntityRegistryEntrySnapshot({
'aliases': list([
None,
@@ -36,7 +363,7 @@
'unit_of_measurement': None,
})
# ---
# name: test_all_entities[sensor.echo_test_next_alarm-state]
# name: test_all_entities[echo][sensor.echo_test_next_alarm-state]
StateSnapshot({
'attributes': ReadOnlyDict({
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'timestamp',
@@ -50,7 +377,7 @@
'state': '2023-10-01T07:00:00+00:00',
})
# ---
# name: test_all_entities[sensor.echo_test_next_reminder-entry]
# name: test_all_entities[echo][sensor.echo_test_next_reminder-entry]
EntityRegistryEntrySnapshot({
'aliases': list([
None,
@@ -87,7 +414,7 @@
'unit_of_measurement': None,
})
# ---
# name: test_all_entities[sensor.echo_test_next_reminder-state]
# name: test_all_entities[echo][sensor.echo_test_next_reminder-state]
StateSnapshot({
'attributes': ReadOnlyDict({
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'timestamp',
@@ -101,7 +428,7 @@
'state': 'unavailable',
})
# ---
# name: test_all_entities[sensor.echo_test_next_timer-entry]
# name: test_all_entities[echo][sensor.echo_test_next_timer-entry]
EntityRegistryEntrySnapshot({
'aliases': list([
None,
@@ -138,7 +465,7 @@
'unit_of_measurement': None,
})
# ---
# name: test_all_entities[sensor.echo_test_next_timer-state]
# name: test_all_entities[echo][sensor.echo_test_next_timer-state]
StateSnapshot({
'attributes': ReadOnlyDict({
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'timestamp',
@@ -152,7 +479,7 @@
'state': 'unavailable',
})
# ---
# name: test_all_entities[sensor.echo_test_temperature-entry]
# name: test_all_entities[echo][sensor.echo_test_temperature-entry]
EntityRegistryEntrySnapshot({
'aliases': list([
None,
@@ -194,7 +521,7 @@
'unit_of_measurement': <UnitOfTemperature.CELSIUS: '°C'>,
})
# ---
# name: test_all_entities[sensor.echo_test_temperature-state]
# name: test_all_entities[echo][sensor.echo_test_temperature-state]
StateSnapshot({
'attributes': ReadOnlyDict({
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'temperature',
@@ -17,6 +17,8 @@ from .const import (
TEST_DEVICE_1_SN,
TEST_DEVICE_2,
TEST_DEVICE_2_SN,
TEST_DEVICE_AQM,
TEST_DEVICE_AQM_SN,
TEST_VOCAL_RECORD_EVENT,
)
@@ -149,3 +151,18 @@ async def test_voice_event_removed_for_unsupported_device(
assert not hass.states.get(entity.entity_id)
assert entity_registry.async_get(entity.entity_id) is None
async def test_voice_event_not_created_for_aqm_device(
hass: HomeAssistant,
mock_amazon_devices_client: AsyncMock,
mock_config_entry: MockConfigEntry,
) -> None:
"""Test no voice event entity is created for an Air Quality Monitor device."""
mock_amazon_devices_client.get_devices_data.return_value = {
TEST_DEVICE_AQM_SN: TEST_DEVICE_AQM
}
await setup_integration(hass, mock_config_entry)
assert not hass.states.get("event.air_quality_monitor_test_voice_event")
@@ -40,7 +40,7 @@ from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_registry as er
from . import assert_device_removed_and_readded, setup_integration
from .const import TEST_DEVICE_1, TEST_DEVICE_1_SN
from .const import TEST_DEVICE_1, TEST_DEVICE_1_SN, TEST_DEVICE_AQM, TEST_DEVICE_AQM_SN
from tests.common import MockConfigEntry, async_fire_time_changed, snapshot_platform
@@ -172,8 +172,6 @@ async def test_media_player_not_created_for_unsupported_device(
mock_config_entry: MockConfigEntry,
) -> None:
"""No entity is created for a device that does not support media playback."""
from .const import TEST_DEVICE_1 # noqa: PLC0415
device = deepcopy(TEST_DEVICE_1)
device.media_player_supported = False
mock_amazon_devices_client.get_devices_data.return_value = {
@@ -185,6 +183,21 @@ async def test_media_player_not_created_for_unsupported_device(
assert hass.states.get(ENTITY_ID) is None
async def test_media_player_not_created_for_aqm_device(
hass: HomeAssistant,
mock_amazon_devices_client: AsyncMock,
mock_config_entry: MockConfigEntry,
) -> None:
"""No media player entity is created for an Air Quality Monitor device."""
mock_amazon_devices_client.get_devices_data.return_value = {
TEST_DEVICE_AQM_SN: TEST_DEVICE_AQM
}
await _setup_media_player_platform(hass, mock_config_entry)
assert hass.states.get("media_player.air_quality_monitor_test") is None
@pytest.mark.parametrize(
"side_effect",
[
+20 -2
View File
@@ -1,5 +1,6 @@
"""Tests for the Alexa Devices sensor platform."""
from copy import deepcopy
from typing import Any
from unittest.mock import AsyncMock, patch
@@ -8,7 +9,7 @@ from aioamazondevices.exceptions import (
CannotConnect,
CannotRetrieveData,
)
from aioamazondevices.structures import AmazonDeviceSensor
from aioamazondevices.structures import AmazonDevice, AmazonDeviceSensor
from freezegun.api import FrozenDateTimeFactory
import pytest
from syrupy.assertion import SnapshotAssertion
@@ -19,19 +20,36 @@ from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_registry as er
from . import assert_device_removed_and_readded, setup_integration
from .const import TEST_DEVICE_1, TEST_DEVICE_1_SN, TEST_DEVICE_2, TEST_DEVICE_2_SN
from .const import (
TEST_DEVICE_1,
TEST_DEVICE_1_SN,
TEST_DEVICE_2,
TEST_DEVICE_2_SN,
TEST_DEVICE_AQM,
TEST_DEVICE_AQM_SN,
)
from tests.common import MockConfigEntry, async_fire_time_changed, snapshot_platform
@pytest.mark.parametrize(
"devices",
[
pytest.param({TEST_DEVICE_1_SN: TEST_DEVICE_1}, id="echo"),
pytest.param({TEST_DEVICE_AQM_SN: TEST_DEVICE_AQM}, id="aqm"),
],
)
async def test_all_entities(
hass: HomeAssistant,
snapshot: SnapshotAssertion,
mock_amazon_devices_client: AsyncMock,
mock_config_entry: MockConfigEntry,
entity_registry: er.EntityRegistry,
devices: dict[str, AmazonDevice],
) -> None:
"""Test all entities."""
mock_amazon_devices_client.get_devices_data.return_value = deepcopy(devices)
with patch("homeassistant.components.alexa_devices.PLATFORMS", [Platform.SENSOR]):
await setup_integration(hass, mock_config_entry)