diff --git a/homeassistant/components/wirelesstag/sensor.py b/homeassistant/components/wirelesstag/sensor.py index 09b70ded5ba0..061192248833 100644 --- a/homeassistant/components/wirelesstag/sensor.py +++ b/homeassistant/components/wirelesstag/sensor.py @@ -19,6 +19,7 @@ from homeassistant.helpers import config_validation as cv from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType +from homeassistant.util import slugify from . import WirelessTagPlatform from .const import DOMAIN, SIGNAL_TAG_UPDATE, WIRELESSTAG_DATA @@ -115,8 +116,12 @@ class WirelessTagSensor(WirelessTagBaseSensor, SensorEntity): # I want to see entity_id as: # sensor.wirelesstag_bedroom_temperature # and not as sensor.bedroom for temperature and - # sensor.bedroom_2 for humidity - self.entity_id = f"sensor.{DOMAIN}_{self.underscored_name}_{self._sensor_type}" + # sensor.bedroom_2 for humidity. + # slugify ensures the entity_id stays valid for tag names containing + # accented/international or special characters. + self.entity_id = ( + f"sensor.{slugify(f'{DOMAIN}_{self._tag.name}_{self._sensor_type}')}" + ) @override async def async_added_to_hass(self) -> None: @@ -129,11 +134,6 @@ class WirelessTagSensor(WirelessTagBaseSensor, SensorEntity): ) ) - @property - def underscored_name(self): - """Provide name savvy to be used in entity_id name of self.""" - return self.name.lower().replace(" ", "_") - @property @override def native_value(self): diff --git a/tests/components/wirelesstag/test_sensor.py b/tests/components/wirelesstag/test_sensor.py new file mode 100644 index 000000000000..0a301842038a --- /dev/null +++ b/tests/components/wirelesstag/test_sensor.py @@ -0,0 +1,89 @@ +"""Tests for the Wireless Sensor Tags sensor platform.""" + +from unittest.mock import MagicMock, patch + +import pytest + +from homeassistant.const import PERCENTAGE, UnitOfTemperature +from homeassistant.core import HomeAssistant, valid_entity_id +from homeassistant.setup import async_setup_component + +CONFIG = { + "wirelesstag": {"username": "foo@bar.com", "password": "secret"}, + "sensor": { + "platform": "wirelesstag", + "monitored_conditions": ["temperature", "humidity"], + }, +} + + +def _mock_tag(name: str) -> MagicMock: + """Return a mocked wirelesstagpy SensorTag exposing temperature and humidity.""" + tag = MagicMock() + tag.uuid = "00000000-0000-0000-0000-000000000001" + tag.tag_id = 1 + tag.tag_manager_mac = "ABCDEF012345" + tag.name = name + tag.allowed_sensor_types = ["temperature", "humidity"] + tag.is_alive = True + tag.battery_remaining = 0.85 + tag.battery_volts = 3.0 + tag.signal_strength = -60 + tag.is_in_range = True + tag.power_consumption = 1.5 + + def _sensor(sensor_type: str) -> MagicMock: + sensor = MagicMock() + if sensor_type == "temperature": + sensor.value = 21.5 + sensor.unit = UnitOfTemperature.CELSIUS + else: + sensor.value = 45.0 + sensor.unit = PERCENTAGE + return sensor + + tag.sensor.__getitem__.side_effect = _sensor + return tag + + +@pytest.mark.parametrize( + ("tag_name", "expected_entity_id"), + [ + pytest.param( + "Bedroom", + "sensor.wirelesstag_bedroom_temperature", + id="ascii_name", + ), + pytest.param( + "Küche", + "sensor.wirelesstag_kuche_temperature", + id="non_ascii_name", + ), + ], +) +async def test_sensor_entity_id_is_valid( + hass: HomeAssistant, + caplog: pytest.LogCaptureFixture, + tag_name: str, + expected_entity_id: str, +) -> None: + """Test sensors get a valid entity_id even for non-ASCII tag names. + + A tag named e.g. "Küche" previously produced the invalid entity_id + "sensor.wirelesstag_küche_temperature", which is rejected by Home + Assistant's entity ID validation. + """ + tag = _mock_tag(tag_name) + with patch("homeassistant.components.wirelesstag.WirelessTags") as mock_api_class: + mock_api = mock_api_class.return_value + mock_api.load_tags.return_value = {tag.uuid: tag} + + assert await async_setup_component(hass, "wirelesstag", CONFIG) + await hass.async_block_till_done() + assert await async_setup_component(hass, "sensor", CONFIG) + await hass.async_block_till_done() + + state = hass.states.get(expected_entity_id) + assert state is not None + assert valid_entity_id(state.entity_id) + assert "sets an invalid entity ID" not in caplog.text