Prevent empty aliases in registries (#156061)

Co-authored-by: J. Diego Rodríguez Royo <jdrr1998@hotmail.com>
Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
This commit is contained in:
Federico Imberti
2025-12-15 22:28:53 +01:00
committed by Bram Kragten
parent 0547153730
commit 67550731b3
6 changed files with 208 additions and 10 deletions

View File

@@ -65,8 +65,10 @@ def websocket_create_area(
data.pop("id")
if "aliases" in data:
# Convert aliases to a set
data["aliases"] = set(data["aliases"])
# Create a set for the aliases without:
# - Empty strings
# - Trailing and leading whitespace characters in the individual aliases
data["aliases"] = {s_strip for s in data["aliases"] if (s_strip := s.strip())}
if "labels" in data:
# Convert labels to a set
@@ -133,8 +135,10 @@ def websocket_update_area(
data.pop("id")
if "aliases" in data:
# Convert aliases to a set
data["aliases"] = set(data["aliases"])
# Create a set for the aliases without:
# - Empty strings
# - Trailing and leading whitespace characters in the individual aliases
data["aliases"] = {s_strip for s in data["aliases"] if (s_strip := s.strip())}
if "labels" in data:
# Convert labels to a set

View File

@@ -227,8 +227,10 @@ def websocket_update_entity(
changes[key] = msg[key]
if "aliases" in msg:
# Convert aliases to a set
changes["aliases"] = set(msg["aliases"])
# Create a set for the aliases without:
# - Empty strings
# - Trailing and leading whitespace characters in the individual aliases
changes["aliases"] = {s_strip for s in msg["aliases"] if (s_strip := s.strip())}
if "labels" in msg:
# Convert labels to a set

View File

@@ -61,8 +61,10 @@ def websocket_create_floor(
data.pop("id")
if "aliases" in data:
# Convert aliases to a set
data["aliases"] = set(data["aliases"])
# Create a set for the aliases without:
# - Empty strings
# - Trailing and leading whitespace characters in the individual aliases
data["aliases"] = {s_strip for s in data["aliases"] if (s_strip := s.strip())}
try:
entry = registry.async_create(**data)
@@ -117,8 +119,10 @@ def websocket_update_floor(
data.pop("id")
if "aliases" in data:
# Convert aliases to a set
data["aliases"] = set(data["aliases"])
# Create a set for the aliases without:
# - Empty strings
# - Trailing and leading whitespace characters in the individual aliases
data["aliases"] = {s_strip for s in data["aliases"] if (s_strip := s.strip())}
try:
entry = registry.async_update(**data)

View File

@@ -172,6 +172,39 @@ async def test_create_area(
}
assert len(area_registry.areas) == 2
# Create area with invalid aliases
await client.send_json_auto_id(
{
"aliases": [" alias_1 ", "", " "],
"floor_id": "first_floor",
"icon": "mdi:garage",
"labels": ["label_1", "label_2"],
"name": "mock 3",
"picture": "/image/example.png",
"temperature_entity_id": "sensor.mock_temperature",
"humidity_entity_id": "sensor.mock_humidity",
"type": "config/area_registry/create",
}
)
msg = await client.receive_json()
assert msg["success"]
assert msg["result"] == {
"aliases": unordered(["alias_1"]),
"area_id": ANY,
"floor_id": "first_floor",
"icon": "mdi:garage",
"labels": unordered(["label_1", "label_2"]),
"name": "mock 3",
"picture": "/image/example.png",
"created_at": utcnow().timestamp(),
"modified_at": utcnow().timestamp(),
"temperature_entity_id": "sensor.mock_temperature",
"humidity_entity_id": "sensor.mock_humidity",
}
assert len(area_registry.areas) == 3
async def test_create_area_with_name_already_in_use(
client: MockHAClientWebSocket, area_registry: ar.AreaRegistry
@@ -304,6 +337,40 @@ async def test_update_area(
}
assert len(area_registry.areas) == 1
modified_at = datetime.fromisoformat("2024-07-16T13:55:00.900075+00:00")
freezer.move_to(modified_at)
await client.send_json_auto_id(
{
"type": "config/area_registry/update",
"aliases": ["alias_1", "", " ", " alias_2 "],
"area_id": area.id,
"floor_id": None,
"humidity_entity_id": None,
"icon": None,
"labels": [],
"picture": None,
"temperature_entity_id": None,
}
)
msg = await client.receive_json()
assert msg["result"] == {
"aliases": unordered(["alias_1", "alias_2"]),
"area_id": area.id,
"floor_id": None,
"icon": None,
"labels": [],
"name": "mock 2",
"picture": None,
"temperature_entity_id": None,
"humidity_entity_id": None,
"created_at": created_at.timestamp(),
"modified_at": modified_at.timestamp(),
}
assert len(area_registry.areas) == 1
async def test_update_area_with_same_name(
client: MockHAClientWebSocket, area_registry: ar.AreaRegistry

View File

@@ -887,6 +887,49 @@ async def test_update_entity(
},
}
# Add illegal terms to aliases
await client.send_json_auto_id(
{
"type": "config/entity_registry/update",
"entity_id": "test_domain.world",
"aliases": ["alias_1", "alias_2", "", " alias_3 ", " "],
}
)
msg = await client.receive_json()
assert msg["success"]
assert msg["result"] == {
"entity_entry": {
"aliases": unordered(["alias_1", "alias_2", "alias_3"]),
"area_id": "mock-area-id",
"capabilities": None,
"categories": {"scope1": "id", "scope3": "other_id"},
"config_entry_id": None,
"config_subentry_id": None,
"created_at": created.timestamp(),
"device_class": "custom_device_class",
"device_id": None,
"disabled_by": None,
"entity_category": None,
"entity_id": "test_domain.world",
"has_entity_name": False,
"hidden_by": "user", # We exchange strings over the WS API, not enums
"icon": "icon:after update",
"id": ANY,
"labels": unordered(["label1", "label2"]),
"modified_at": modified.timestamp(),
"name": "after update",
"options": {"sensor": {"unit_of_measurement": "beard_second"}},
"original_device_class": None,
"original_icon": None,
"original_name": None,
"platform": "test_platform",
"translation_key": None,
"unique_id": "1234",
},
}
async def test_update_entity_require_restart(
hass: HomeAssistant, client: MockHAClientWebSocket, freezer: FrozenDateTimeFactory

View File

@@ -122,6 +122,30 @@ async def test_create_floor(
"level": 2,
}
# Floor with invalid aliases
await client.send_json_auto_id(
{
"name": "Third floor",
"type": "config/floor_registry/create",
"aliases": ["", " "],
"icon": "mdi:home-floor-2",
"level": 3,
}
)
msg = await client.receive_json()
assert len(floor_registry.floors) == 3
assert msg["result"] == {
"aliases": [],
"created_at": utcnow().timestamp(),
"icon": "mdi:home-floor-2",
"floor_id": "third_floor",
"modified_at": utcnow().timestamp(),
"name": "Third floor",
"level": 3,
}
async def test_create_floor_with_name_already_in_use(
client: MockHAClientWebSocket,
@@ -249,6 +273,60 @@ async def test_update_floor(
"level": None,
}
# Add invalid aliases
modified_at = datetime.fromisoformat("2024-07-16T13:55:00.900075+00:00")
freezer.move_to(modified_at)
await client.send_json_auto_id(
{
"floor_id": floor.floor_id,
"name": "First floor",
"aliases": ["top floor", "attic", "", " "],
"icon": None,
"level": None,
"type": "config/floor_registry/update",
}
)
msg = await client.receive_json()
assert len(floor_registry.floors) == 1
assert msg["result"] == {
"aliases": unordered(["top floor", "attic"]),
"created_at": created_at.timestamp(),
"icon": None,
"floor_id": floor.floor_id,
"modified_at": modified_at.timestamp(),
"name": "First floor",
"level": None,
}
# Add alias with trailing and leading whitespaces
modified_at = datetime.fromisoformat("2024-07-16T13:55:00.900075+00:00")
freezer.move_to(modified_at)
await client.send_json_auto_id(
{
"floor_id": floor.floor_id,
"name": "First floor",
"aliases": ["top floor", "attic", "solaio "],
"icon": None,
"level": None,
"type": "config/floor_registry/update",
}
)
msg = await client.receive_json()
assert len(floor_registry.floors) == 1
assert msg["result"] == {
"aliases": unordered(["top floor", "attic", "solaio"]),
"created_at": created_at.timestamp(),
"icon": None,
"floor_id": floor.floor_id,
"modified_at": modified_at.timestamp(),
"name": "First floor",
"level": None,
}
async def test_update_with_name_already_in_use(
client: MockHAClientWebSocket,